| 1 | /* | 
| 2 | *  jDTAUS Core Utilities | 
| 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.core.io.util; | 
| 22 |  | 
| 23 | import java.io.IOException; | 
| 24 | import java.io.InputStream; | 
| 25 | import java.io.OutputStream; | 
| 26 | import java.io.Serializable; | 
| 27 | import java.util.Arrays; | 
| 28 | import java.util.Locale; | 
| 29 | import org.jdtaus.core.container.ContainerFactory; | 
| 30 | import org.jdtaus.core.io.FileOperations; | 
| 31 | import org.jdtaus.core.lang.spi.MemoryManager; | 
| 32 | import org.jdtaus.core.logging.spi.Logger; | 
| 33 |  | 
| 34 | /** | 
| 35 | * Implementation of elementary I/O operations in heap memory. | 
| 36 | * <p>This implementation performs I/O in memory. The value of property | 
| 37 | * {@code length} is limited to a maximum of {@code Integer.MAX_VALUE} (4 GB). | 
| 38 | * </p> | 
| 39 | * | 
| 40 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> | 
| 41 | * @version $JDTAUS: MemoryFileOperations.java 8853 2014-01-10 13:50:00Z schulte $ | 
| 42 | */ | 
| 43 | public final class MemoryFileOperations | 
| 44 | implements FileOperations, Serializable, Cloneable | 
| 45 | { | 
| 46 | //--Fields------------------------------------------------------------------ | 
| 47 |  | 
| 48 | /** | 
| 49 | * Data to operate on. | 
| 50 | * @serial | 
| 51 | */ | 
| 52 | private byte[] data; | 
| 53 |  | 
| 54 | /** | 
| 55 | * FilePointer. | 
| 56 | * @serial | 
| 57 | */ | 
| 58 | private long filePointer; | 
| 59 |  | 
| 60 | /** | 
| 61 | * Actual length. | 
| 62 | * @serial | 
| 63 | */ | 
| 64 | private int length; | 
| 65 |  | 
| 66 | /** | 
| 67 | * Default temporary buffer. | 
| 68 | * @serial | 
| 69 | */ | 
| 70 | private byte[] defaultBuffer; | 
| 71 |  | 
| 72 | //------------------------------------------------------------------Fields-- | 
| 73 | //--Properties-------------------------------------------------------------- | 
| 74 |  | 
| 75 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties | 
| 76 | // This section is managed by jdtaus-container-mojo. | 
| 77 |  | 
| 78 | /** | 
| 79 | * Gets the value of property <code>streamBufferSize</code>. | 
| 80 | * | 
| 81 | * @return Size of the buffer for buffering streams. | 
| 82 | */ | 
| 83 | private int getStreamBufferSize() | 
| 84 | { | 
| 85 | return ( (java.lang.Integer) ContainerFactory.getContainer(). | 
| 86 | getProperty( this, "streamBufferSize" ) ).intValue(); | 
| 87 |  | 
| 88 | } | 
| 89 |  | 
| 90 | // </editor-fold>//GEN-END:jdtausProperties | 
| 91 |  | 
| 92 | //--------------------------------------------------------------Properties-- | 
| 93 | //--Dependencies------------------------------------------------------------ | 
| 94 |  | 
| 95 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies | 
| 96 | // This section is managed by jdtaus-container-mojo. | 
| 97 |  | 
| 98 | /** | 
| 99 | * 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 = (byte[]) buf.clone(); | 
| 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 ( final 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 | } |