001/*
002 *  jDTAUS Core Utilities
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.core.io.util;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.io.Serializable;
027import java.util.Arrays;
028import java.util.Locale;
029import org.jdtaus.core.container.ContainerFactory;
030import org.jdtaus.core.io.FileOperations;
031import org.jdtaus.core.lang.spi.MemoryManager;
032import org.jdtaus.core.logging.spi.Logger;
033
034/**
035 * Implementation of elementary I/O operations in heap memory.
036 * <p>This implementation performs I/O in memory. The value of property
037 * {@code length} is limited to a maximum of {@code Integer.MAX_VALUE} (4 GB).
038 * </p>
039 *
040 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
041 * @version $JDTAUS: MemoryFileOperations.java 8644 2012-09-27 06:46:11Z schulte $
042 */
043public final class MemoryFileOperations
044    implements FileOperations, Serializable, Cloneable
045{
046    //--Fields------------------------------------------------------------------
047
048    /**
049     * Data to operate on.
050     * @serial
051     */
052    private byte[] data;
053
054    /**
055     * FilePointer.
056     * @serial
057     */
058    private long filePointer;
059
060    /**
061     * Actual length.
062     * @serial
063     */
064    private int length;
065
066    /**
067     * Default temporary buffer.
068     * @serial
069     */
070    private byte[] defaultBuffer;
071
072    //------------------------------------------------------------------Fields--
073    //--Properties--------------------------------------------------------------
074
075// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
076    // This section is managed by jdtaus-container-mojo.
077
078    /**
079     * Gets the value of property <code>streamBufferSize</code>.
080     *
081     * @return Size of the buffer for buffering streams.
082     */
083    private int getStreamBufferSize()
084    {
085        return ( (java.lang.Integer) ContainerFactory.getContainer().
086            getProperty( this, "streamBufferSize" ) ).intValue();
087
088    }
089
090// </editor-fold>//GEN-END:jdtausProperties
091
092    //--------------------------------------------------------------Properties--
093    //--Dependencies------------------------------------------------------------
094
095// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
096    // This section is managed by jdtaus-container-mojo.
097
098    /**
099     * Gets the configured <code>Logger</code> implementation.
100     *
101     * @return The configured <code>Logger</code> implementation.
102     */
103    private Logger getLogger()
104    {
105        return (Logger) ContainerFactory.getContainer().
106            getDependency( this, "Logger" );
107
108    }
109
110    /**
111     * Gets the configured <code>MemoryManager</code> implementation.
112     *
113     * @return The configured <code>MemoryManager</code> implementation.
114     */
115    private MemoryManager getMemoryManager()
116    {
117        return (MemoryManager) ContainerFactory.getContainer().
118            getDependency( this, "MemoryManager" );
119
120    }
121
122    /**
123     * Gets the configured <code>Locale</code> implementation.
124     *
125     * @return The configured <code>Locale</code> implementation.
126     */
127    private Locale getLocale()
128    {
129        return (Locale) ContainerFactory.getContainer().
130            getDependency( this, "Locale" );
131
132    }
133
134// </editor-fold>//GEN-END:jdtausDependencies
135
136    //------------------------------------------------------------Dependencies--
137    //--FileOperations----------------------------------------------------------
138
139    public long getLength()
140    {
141        return this.length;
142    }
143
144    /**
145     * {@inheritDoc}
146     *
147     * @throws IllegalArgumentException if {@code newLength} is negative or
148     * greater than {@code Integer.MAX_VALUE}.
149     */
150    public void setLength( final long newLength )
151    {
152        if ( newLength < 0L || newLength > Integer.MAX_VALUE )
153        {
154            throw new IllegalArgumentException( Long.toString( newLength ) );
155        }
156
157        this.ensureCapacity( (int) newLength );
158        this.length = (int) newLength;
159        if ( this.filePointer > this.length )
160        {
161            this.filePointer = this.length;
162        }
163    }
164
165    public long getFilePointer()
166    {
167        return this.filePointer;
168    }
169
170    public void setFilePointer( final long pos )
171    {
172        // Preconditions.
173        if ( pos < 0L || pos > Integer.MAX_VALUE )
174        {
175            throw new IllegalArgumentException( Long.toString( pos ) );
176        }
177
178        this.filePointer = pos;
179    }
180
181    public int read( final byte[] buf, final int off, final int len )
182    {
183        final int ret;
184
185        // Preconditions.
186        if ( buf == null )
187        {
188            throw new NullPointerException( "buf" );
189        }
190        if ( off < 0 )
191        {
192            throw new ArrayIndexOutOfBoundsException( off );
193        }
194        if ( len < 0 )
195        {
196            throw new ArrayIndexOutOfBoundsException( len );
197        }
198        if ( off + len > buf.length )
199        {
200            throw new ArrayIndexOutOfBoundsException( off + len );
201        }
202        if ( this.filePointer + len > Integer.MAX_VALUE )
203        {
204            throw new ArrayIndexOutOfBoundsException( Integer.MAX_VALUE );
205        }
206
207        if ( len == 0 )
208        {
209            ret = 0;
210        }
211        else if ( this.filePointer >= this.length )
212        {
213            // EOF
214            ret = FileOperations.EOF;
215        }
216        else if ( this.filePointer + len > this.length )
217        {
218            // less than len byte before EOF
219            final int remaining = (int) ( this.length - this.filePointer );
220            System.arraycopy( this.data, (int) this.filePointer, buf, off,
221                              remaining );
222
223            this.filePointer += remaining;
224            ret = remaining;
225        }
226        else
227        {
228            // copy len byte into buf.
229            System.arraycopy(
230                this.data, (int) this.filePointer, buf, off, len );
231
232            this.filePointer += len;
233            ret = len;
234        }
235
236        return ret;
237    }
238
239    public void write( final byte[] buf, final int off, final int len )
240    {
241        // Preconditions.
242        if ( buf == null )
243        {
244            throw new NullPointerException( "buf" );
245        }
246        if ( off < 0 )
247        {
248            throw new ArrayIndexOutOfBoundsException( off );
249        }
250        if ( len < 0 )
251        {
252            throw new ArrayIndexOutOfBoundsException( len );
253        }
254        if ( off + len > buf.length )
255        {
256            throw new ArrayIndexOutOfBoundsException( off + len );
257        }
258
259        final long newLen = this.filePointer + len;
260        if ( newLen > Integer.MAX_VALUE )
261        {
262            throw new ArrayIndexOutOfBoundsException( Integer.MAX_VALUE );
263        }
264
265        if ( newLen > this.length )
266        {
267            this.setLength( newLen );
268        }
269
270        System.arraycopy( buf, off, this.data, (int) this.filePointer, len );
271        this.filePointer += len;
272    }
273
274    public void read( final OutputStream out ) throws IOException
275    {
276        if ( out == null )
277        {
278            throw new NullPointerException( "out" );
279        }
280
281        out.write( this.data, 0, this.length );
282        this.filePointer = this.length;
283    }
284
285    public void write( final InputStream in ) throws IOException
286    {
287        if ( in == null )
288        {
289            throw new NullPointerException( "in" );
290        }
291
292        int read;
293        final byte[] buf = this.getStreamBuffer();
294
295        while ( ( read = in.read( buf, 0, buf.length ) ) != FileOperations.EOF )
296        {
297            this.write( buf, 0, read );
298        }
299    }
300
301    /**
302     * {@inheritDoc}
303     * <p>Since this implementation does not use any system resources to
304     * release, this method does nothing. In contrast to other
305     * {@code FileOperations} implementations the instance can still be used
306     * after calling this method. It would be a mistake to write an application
307     * which relies on this behaviour, however.</p>
308     */
309    public void close()
310    {
311    }
312
313    //----------------------------------------------------------FileOperations--
314    //--MemoryFileOperations----------------------------------------------------
315
316    /** Creates a new {@code MemoryFileOperations} instance of no length. */
317    public MemoryFileOperations()
318    {
319        this.filePointer = 0L;
320        this.data = new byte[ 0 ];
321        this.length = 0;
322    }
323
324    /**
325     * Creates a new {@code MemoryFileOperations} instance of no length
326     * taking an initial capacity.
327     *
328     * @param initialCapacity the number of bytes to pre-allocate when
329     * creating the new instance.
330     *
331     * @throws OutOfMemoryError if not enough memory is available to create a
332     * buffer with a length of {@code initialCapacity}.
333     *
334     * @see #ensureCapacity(int)
335     */
336    public MemoryFileOperations( final int initialCapacity )
337    {
338        this.filePointer = 0L;
339        this.length = 0;
340        this.data = this.getMemoryManager().allocateBytes( initialCapacity );
341    }
342
343    /**
344     * Creates a new {@code MemoryFileOperations} instance holding {@code buf}.
345     *
346     * @param buf bytes to initialize the instance with.
347     *
348     * @throws NullPointerException if {@code buf} is {@code null}.
349     */
350    public MemoryFileOperations( final byte[] buf )
351    {
352        this();
353
354        if ( buf == null )
355        {
356            throw new NullPointerException( "buf" );
357        }
358
359        this.data = buf;
360        this.length = buf.length;
361        this.filePointer = 0L;
362    }
363
364    /**
365     * Gets the capacity of the instance.
366     *
367     * @return the capacity of the instance in byte.
368     */
369    public int getCapacity()
370    {
371        return this.data.length;
372    }
373
374    /**
375     * Increases the capacity of the instance, if necessary, to ensure that it
376     * can hold at least the number of bytes specified by the minimum capacity
377     * argument.
378     *
379     * @param minimumCapacity the minimum capacity to ensure.
380     */
381    public void ensureCapacity( final int minimumCapacity )
382    {
383        final int oldLength = this.data.length;
384
385        while ( this.data.length < minimumCapacity )
386        {
387            final byte[] newData = this.getMemoryManager().allocateBytes(
388                this.data.length * 2 >= minimumCapacity
389                ? this.data.length * 2
390                : minimumCapacity );
391
392            Arrays.fill( newData, (byte) 0 );
393            System.arraycopy( this.data, 0, newData, 0, this.data.length );
394            this.data = newData;
395        }
396
397        if ( oldLength != this.data.length &&
398             this.getLogger().isDebugEnabled() )
399        {
400            this.getLogger().debug(
401                this.getLogResizeMessage( this.getLocale(),
402                                          new Long( this.data.length ) ) );
403
404        }
405    }
406
407    /**
408     * Gets the file contents.
409     *
410     * @return the file contents of the instance.
411     *
412     * @throws OutOfMemoryError if not enough memory is available.
413     */
414    public byte[] getData()
415    {
416        final int len = (int) this.getLength();
417        final byte[] ret = this.getMemoryManager().allocateBytes( len );
418
419        System.arraycopy( this.data, 0, ret, 0, len );
420
421        return ret;
422    }
423
424    /**
425     * Gets a buffer for buffering streams.
426     *
427     * @return a buffer for buffering streams.
428     */
429    private byte[] getStreamBuffer()
430    {
431        if ( this.defaultBuffer == null )
432        {
433            this.defaultBuffer = this.getMemoryManager().
434                allocateBytes( this.getStreamBufferSize() < 0
435                               ? 0
436                               : this.getStreamBufferSize() );
437
438        }
439
440        return this.defaultBuffer;
441    }
442
443    //----------------------------------------------------MemoryFileOperations--
444    //--Object------------------------------------------------------------------
445
446    /**
447     * Indicates whether some other object is equal to this one by comparing
448     * the contents from memory.
449     *
450     * @param o the reference object with which to compare.
451     *
452     * @return {@code true} if this object is the same as {@code o};
453     * {@code false} otherwise.
454     */
455    public boolean equals( final Object o )
456    {
457        boolean equal = this == o;
458
459        if ( !equal && o instanceof MemoryFileOperations )
460        {
461            final MemoryFileOperations that = (MemoryFileOperations) o;
462            equal = Arrays.equals( getData(), that.getData() );
463        }
464
465        return equal;
466    }
467
468    /**
469     * Returns a hash code value for this object.
470     *
471     * @return a hash code value for this object.
472     */
473    public int hashCode()
474    {
475        // JDK: As of JDK 1.5, Arrays.hashCode( getData() ).
476        final byte[] bytes = getData();
477
478        int result = 1;
479        for ( int i = 0, s0 = bytes.length; i < s0; i++ )
480        {
481            result = 31 * result + bytes[i];
482        }
483
484        return result;
485    }
486
487    /**
488     * Creates and returns a deep copy of this object.
489     *
490     * @return a clone of this instance.
491     */
492    public Object clone()
493    {
494        try
495        {
496            return super.clone();
497        }
498        catch ( CloneNotSupportedException e )
499        {
500            throw new AssertionError( e );
501        }
502    }
503
504    //------------------------------------------------------------------Object--
505    //--Messages----------------------------------------------------------------
506
507// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
508    // This section is managed by jdtaus-container-mojo.
509
510    /**
511     * Gets the text of message <code>logResize</code>.
512     * <blockquote><pre>aktuelle Puffergröße: {0, number} Byte</pre></blockquote>
513     * <blockquote><pre>current buffer size: {0, number} byte</pre></blockquote>
514     *
515     * @param locale The locale of the message instance to return.
516     * @param bufferSize The current size of the internal buffer.
517     *
518     * @return Information about the size of the internal buffer.
519     */
520    private String getLogResizeMessage( final Locale locale,
521            final java.lang.Number bufferSize )
522    {
523        return ContainerFactory.getContainer().
524            getMessage( this, "logResize", locale,
525                new Object[]
526                {
527                    bufferSize
528                });
529
530    }
531
532// </editor-fold>//GEN-END:jdtausMessages
533
534    //----------------------------------------------------------------Messages--
535}