001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.io;
020
021 import com.mucommander.Debug;
022
023 import java.nio.ByteBuffer;
024 import java.nio.CharBuffer;
025 import java.util.Vector;
026
027 /**
028 * This class allows to share and reuse byte buffers to avoid excessive memory allocation and garbage collection.
029 * Methods that use byte buffers and that are called repeatedly will benefit from using this class.
030 *
031 * <p>This class works with two types of byte buffers indifferently:
032 * <ul>
033 * <li>Byte array buffers (<code>byte[]</code>)</li>
034 * <li><code>java.nio.ByteBuffer</code></li>
035 * </ul>
036 *
037 * <p>
038 * Usage of this class is similar to malloc/free:
039 * <ul>
040 * <li>Call <code>#get*Buffer(int)</code> to retrieve a buffer instance of a specified size</li>
041 * <li>Use the buffer</li>
042 * <li>When finished using the buffer, call <code>#release*Buffer(byte[])</code> to make this buffer available for
043 * subsequent calls to <code>#get*Buffer(int)</code>. Failing to call this method will prevent the buffer from being
044 * used again and from being garbage-collected.</li>
045 * </ul>
046 * </p>
047 *
048 * <p>Note: this class is thread safe and thus can safely be used by concurrent threads.</p>
049 *
050 * @author Maxence Bernard
051 * @see com.mucommander.io.StreamUtils
052 */
053 public class BufferPool {
054
055 /** List of BufferContainer instances that wraps available buffers */
056 private static Vector bufferContainers = new Vector();
057
058 /** The initial default buffer size */
059 public final static int INITIAL_DEFAULT_BUFFER_SIZE = 65536;
060
061 /** Size of buffers returned by get*Buffer methods without a size argument */
062 public static int defaultBufferSize = INITIAL_DEFAULT_BUFFER_SIZE;
063
064 /** The initial max pool size */
065 public final static long INITIAL_POOL_LIMIT = 10485760;
066
067 /** Maximum combined size of all pooled buffers, in bytes */
068 public static long maxPoolSize = INITIAL_POOL_LIMIT;
069
070 /** Current combined size of all pooled buffers, in bytes */
071 public static long poolSize;
072
073
074 /**
075 * Convenience method that has the same effect as calling {@link #getByteArray(int)} with
076 * a length equal to {@link #getDefaultBufferSize()}.
077 *
078 * @return a byte array with a length of {@link # getDefaultBufferSize ()}
079 */
080 public static synchronized byte[] getByteArray() {
081 return getByteArray(getDefaultBufferSize());
082 }
083
084 /**
085 * Returns a byte array of the specified length. This method first checks if a byte array of the specified length
086 * exists in the pool. If one is found, it is removed from the pool and returned. If not, a new instance is created
087 * and returned.
088 *
089 * <p>This method won't return the same buffer instance until it has been released with
090 * {@link #releaseByteArray(byte[])}.</p>
091 *
092 * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.io.BufferPool.BufferFactory,int)} called
093 * with a {@link com.mucommander.io.BufferPool.ByteArrayFactory} instance.</p>.
094 *
095 * @param length length of the byte array
096 * @return a byte array of the specified size
097 */
098 public static synchronized byte[] getByteArray(int length) {
099 return (byte[])getBuffer(new ByteArrayFactory(), length);
100 }
101
102 /**
103 * Convenience method that has the same effect as calling {@link #getCharArray(int)} with
104 * a length equal to {@link #getDefaultBufferSize()}.
105 *
106 * @return a char array with a length of {@link # getDefaultBufferSize}
107 */
108 public static synchronized char[] getCharArray() {
109 return getCharArray(getDefaultBufferSize());
110 }
111
112 /**
113 * Returns a char array of the specified length. This method first checks if a char array of the specified length
114 * exists in the pool. If one is found, it is removed from the pool and returned. If not, a new instance is created
115 * and returned.
116 *
117 * <p>This method won't return the same buffer instance until it has been released with
118 * {@link #releaseCharArray(char[])}.</p>
119 *
120 * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.io.BufferPool.BufferFactory,int)} called
121 * with a {@link com.mucommander.io.BufferPool.CharArrayFactory} instance.</p>.
122 *
123 * @param length length of the char array
124 * @return a char array of the specified length
125 */
126 public static synchronized char[] getCharArray(int length) {
127 return (char[])getBuffer(new CharArrayFactory(), length);
128 }
129
130 /**
131 * Convenience method that has the same effect as calling {@link #getByteBuffer(int)} with
132 * a buffer capacity of {@link #getDefaultBufferSize()}.
133 *
134 * @return a ByteBuffer with a capacity equal to {@link # getDefaultBufferSize}
135 */
136 public static synchronized ByteBuffer getByteBuffer() {
137 return getByteBuffer(getDefaultBufferSize());
138 }
139
140 /**
141 * Returns a ByteBuffer of the specified capacity. This method first checks if a ByteBuffer instance of the
142 * specified capacity exists in the pool. If one is found, it is removed from the pool and returned. If not,
143 * a new instance is created and returned.
144 *
145 * <p>This method won't return the same buffer instance until it has been released with
146 * {@link #releaseByteBuffer(ByteBuffer)}.</p>
147 *
148 * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.io.BufferPool.BufferFactory,int)} called
149 * with a {@link com.mucommander.io.BufferPool.ByteBufferFactory} instance.</p>.
150
151 * @param capacity capacity of the ByteBuffer
152 * @return a ByteBuffer with the specified capacity
153 */
154 public static synchronized ByteBuffer getByteBuffer(int capacity) {
155 return (ByteBuffer)getBuffer(new ByteBufferFactory(), capacity);
156 }
157
158
159 /**
160 * Convenience method that has the same effect as calling {@link #getCharBuffer(int)} with
161 * a buffer capacity of {@link #getDefaultBufferSize()}.
162 *
163 * @return a CharBuffer with a capacity equal to {@link # getDefaultBufferSize}
164 */
165 public static synchronized CharBuffer getCharBuffer() {
166 return getCharBuffer(getDefaultBufferSize());
167 }
168
169 /**
170 * Returns a CharBuffer of the specified capacity. This method first checks if a CharBuffer instance of the
171 * specified capacity exists in the pool. If one is found, it is removed from the pool and returned. If not,
172 * a new instance is created and returned.
173 *
174 * <p>This method won't return the same buffer instance until it has been released with
175 * {@link #releaseCharBuffer(CharBuffer)}.</p>
176 *
177 * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.io.BufferPool.BufferFactory,int)} called
178 * with a {@link com.mucommander.io.BufferPool.CharBufferFactory} instance.</p>.
179
180 * @param capacity capacity of the CharBuffer
181 * @return a CharBuffer with the specified capacity
182 */
183 public static synchronized CharBuffer getCharBuffer(int capacity) {
184 return (CharBuffer)getBuffer(new CharBufferFactory(), capacity);
185 }
186
187
188 /**
189 * Convenience method that has the same effect as calling {@link #getBuffer(com.mucommander.io.BufferPool.BufferFactory, int)}
190 * with a size equal to {@link #getDefaultBufferSize()}.
191 *
192 * @param factory BufferFactory used to identify the target buffer class and create a new buffer (if necessary)
193 * @return a buffer with a size equal to {@link # getDefaultBufferSize}
194 */
195 public static synchronized Object getBuffer(BufferFactory factory) {
196 return getBuffer(factory, getDefaultBufferSize());
197 }
198
199 /**
200 * Returns a byte array of the specified size. This method first checks if a buffer the same size as the specified
201 * one and a class compatible with the specified factory exists in the pool. If one is found, it is removed from the
202 * pool and returned.
203 * If not, a new instance is created and returned using {@link BufferFactory#newBuffer(int)}.
204 *
205 * <p>This method won't return the same buffer instance until it has been released with
206 * {@link #releaseBuffer(Object, BufferFactory)}.</p>
207 *
208 * @param factory BufferFactory used to identify the target buffer class and create a new buffer (if necessary)
209 * @param size size of the buffer
210 * @return a buffer of the specified size
211 */
212 public static synchronized Object getBuffer(BufferFactory factory, int size) {
213 int nbBuffers = bufferContainers.size();
214 BufferContainer bufferContainer;
215 Object buffer;
216
217 // Looks for a buffer container in the pool that matches the specified size and buffer class.
218 for(int i=0; i<nbBuffers; i++) {
219 bufferContainer = (BufferContainer) bufferContainers.elementAt(i);
220 buffer = bufferContainer.getBuffer();
221
222 // Caution: mind the difference between BufferContainer#getLength() and BufferContainer#getSize()
223 if(bufferContainer.getLength()==size && (factory.matchesBufferClass(buffer.getClass()))) {
224 bufferContainers.removeElementAt(i);
225 poolSize -= bufferContainer.getSize();
226 // if(Debug.ON) Debug.trace("Returning buffer "+buffer+", size="+size);
227 return buffer;
228 }
229 }
230
231 if(Debug.ON) Debug.trace("Creating new buffer with "+factory+" size="+size, 3);
232
233 // No buffer with the same class and size found in the pool, create a new one and return it
234 return factory.newBuffer(size);
235 }
236
237
238 /**
239 * Makes the given buffer available for further calls to {@link #getByteArray(int)} with the same buffer length.
240 * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in
241 * the pool.
242 *
243 * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get
244 * corrupted if other threads were using it.</p>
245 *
246 * @param buffer the buffer instance to make available for further use
247 * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool
248 * @throws IllegalArgumentException if specified buffer is null
249 */
250 public static synchronized boolean releaseByteArray(byte buffer[]) {
251 return releaseBuffer(buffer, new ByteArrayFactory());
252 }
253
254 /**
255 * Makes the given buffer available for further calls to {@link #getCharArray(int)} with the same buffer length.
256 * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in
257 * the pool.
258 *
259 * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get
260 * corrupted if other threads were using it.</p>
261 *
262 * @param buffer the buffer instance to make available for further use
263 * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool
264 * @throws IllegalArgumentException if specified buffer is null
265 */
266 public static synchronized boolean releaseCharArray(char buffer[]) {
267 return releaseBuffer(buffer, new CharArrayFactory());
268 }
269
270 /**
271 * Makes the given buffer available for further calls to {@link #getByteBuffer(int)} with the same buffer capacity.
272 * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in
273 * the pool.
274 *
275 * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get
276 * corrupted if other threads were using it.</p>
277 *
278 * @param buffer the buffer instance to make available for further use
279 * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool
280 * @throws IllegalArgumentException if specified buffer is null
281 */
282 public static synchronized boolean releaseByteBuffer(ByteBuffer buffer) {
283 return releaseBuffer(buffer, new ByteBufferFactory());
284 }
285
286 /**
287 * Makes the given buffer available for further calls to {@link #getCharBuffer(int)} with the same buffer capacity.
288 * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in
289 * the pool.
290 *
291 * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get
292 * corrupted if other threads were using it.</p>
293 *
294 * @param buffer the buffer instance to make available for further use
295 * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool
296 * @throws IllegalArgumentException if specified buffer is null
297 */
298 public static synchronized boolean releaseCharBuffer(CharBuffer buffer) {
299 return releaseBuffer(buffer, new CharBufferFactory());
300 }
301
302 /**
303 * Makes the given buffer available for further calls to {@link #getBuffer(com.mucommander.io.BufferPool.BufferFactory,int)} with the same buffer
304 * size and factory.
305 * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in
306 * the pool or the pool size limit has been reached.
307 *
308 * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get
309 * corrupted if other threads were using it.</p>
310 *
311 * @param buffer the buffer instance to make available for further use
312 * @param factory the BufferFactory that was used to create the buffer
313 * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool or the pool size limit has been reached
314 * @throws IllegalArgumentException if specified buffer is null
315 */
316 public static synchronized boolean releaseBuffer(Object buffer, BufferFactory factory) {
317 if(buffer==null)
318 throw new IllegalArgumentException("specified buffer is null");
319
320 BufferContainer bufferContainer = factory.newBufferContainer(buffer);
321
322 if(bufferContainers.contains(bufferContainer)) {
323 if(Debug.ON) Debug.trace("Warning: specified buffer is already in the pool: "+buffer, -1);
324 return false;
325 }
326
327 long bufferSize = bufferContainer.getSize(); // size in bytes (!= length)
328
329 if(maxPoolSize!=-1 && poolSize+bufferSize>maxPoolSize) {
330 if(Debug.ON) Debug.trace("Warning: maximum pool size reached, buffer not added to the pool"+buffer);
331 return false;
332 }
333
334 // if(Debug.ON) Debug.trace("Adding buffer to pool: "+buffer);
335
336 bufferContainers.add(bufferContainer);
337 poolSize += bufferSize;
338
339 return true;
340 }
341
342 /**
343 * Returns <code>true</code> if the specified buffer is currently in the pool.
344 *
345 * <p>Note that it is not necessary (and thus not recommended for performance reasons) to call this method before
346 * calling <code>release*Buffer</code> as it already performs this test before adding a buffer to the pool.</p>
347 *
348 * @param buffer the buffer to look for in the pool
349 * @param factory the BufferFactory that was used to create the buffer
350 * @return <code>true</code> if the specified buffer is already in the pool
351 */
352 public static boolean containsBuffer(Object buffer, BufferFactory factory) {
353 return bufferContainers.contains(factory.newBufferContainer(buffer));
354 }
355
356
357 /**
358 * Returns the number of buffers that currently are in the pool. This method is provided for debugging
359 * purposes only.
360 *
361 * @return the number of buffers currently in the pool
362 */
363 public static int getBufferCount() {
364 return bufferContainers.size();
365 }
366
367 /**
368 * Returns the number of buffers that currently are in the pool and whose Class are the same as the specified
369 * factory's. This method is provided for debugging purposes only.
370 *
371 * @param factory the BufferFactory
372 * @return the number of buffers currently in the pool
373 */
374 public static int getBufferCount(BufferFactory factory) {
375 int count = 0;
376 int nbBuffers = bufferContainers.size();
377 for(int i=0; i<nbBuffers; i++) {
378 if(factory.matchesBufferClass(((BufferContainer)bufferContainers.elementAt(i)).getBuffer().getClass())) {
379 count ++;
380 }
381 }
382
383 return count;
384 }
385
386 /**
387 * Returns the default size of buffers returned by <code>get*Buffer</code> methods without a <code>size</code>
388 * argument.
389 *
390 * <p>The default buffer size is initially set to {@link #INITIAL_DEFAULT_BUFFER_SIZE}.</p>
391 *
392 * @return the default size of buffers returned by <code>get*Buffer</code> methods without a <code>size</code> argument
393 */
394 public static int getDefaultBufferSize() {
395 return defaultBufferSize;
396 }
397
398 /**
399 * Sets the default size of buffers returned by <code>get*Buffer</code> methods without a <code>size</code> argument.
400 *
401 * @param bufferSize the new buffer size
402 */
403 public static synchronized void setDefaultBufferSize(int bufferSize) {
404 BufferPool.defaultBufferSize = bufferSize;
405 }
406
407
408 /**
409 * Returns the combined size in bytes of all buffers that are currently in the pool.
410 *
411 * @return the combined size in bytes of all buffers that are currenty in the pool
412 */
413 public static long getPoolSize() {
414 return poolSize;
415 }
416
417 /**
418 * Returns the maximum combined size in bytes for all buffers in the pool, <code>-1</code> for no limit.
419 * Before adding a buffer to the pool, <code>release*Buffer</code> methods ensure that the pool size will
420 * not be exceeded. If and only if that is the case, the buffer is added to the pool.
421 *
422 * <p>The max pool size is initially set to {@link #INITIAL_POOL_LIMIT}.</p>
423 *
424 * @return the maximum combined size in bytes for all buffers in the pool
425 */
426 public static long getMaxPoolSize() {
427 return maxPoolSize;
428 }
429
430 /**
431 * Sets the maximum combined size in bytes for all buffers in the pool, <code>-1</code> for no limit.
432 * Before adding a buffer to the pool, <code>release*Buffer</code> methods ensure that the pool size will
433 * not be exceeded. If and only if that is the case, the buffer is added to the pool.
434 *
435 * @param maxPoolSize the maximum combined size in bytes for all buffers in the pool
436 */
437 public static synchronized void setMaxPoolSize(long maxPoolSize) {
438 BufferPool.maxPoolSize = maxPoolSize;
439 }
440
441
442 ///////////////////
443 // Inner classes //
444 ///////////////////
445
446 /**
447 * Wraps a buffer instance and provides information about the wrapped buffer.
448 */
449 public static abstract class BufferContainer {
450
451 /** The wrapped buffer instance */
452 protected Object buffer;
453
454 /**
455 * Creates a new BufferContainer that wraps the given buffer.
456 *
457 * @param buffer the buffer instance to wrap
458 */
459 protected BufferContainer(Object buffer) {
460 this.buffer = buffer;
461 }
462
463 /**
464 * Returns the wrapped buffer instance.
465 *
466 * @return the wrapped buffer instance
467 */
468 protected Object getBuffer() {
469 return buffer;
470 }
471
472 /**
473 * Implements a shallow equal comparison.
474 */
475 public boolean equals(Object o) {
476 // Note: this method is used by Vector.contains()
477 return (o instanceof BufferContainer) && buffer == ((BufferContainer)o).buffer;
478 }
479
480 /**
481 * Returns the length of the wrapped buffer instance.
482 *
483 * @return the length of the wrapped buffer instance
484 */
485 protected abstract int getLength();
486
487 /**
488 * Returns the size of the wrapped buffer instance, expressed in bytes.
489 *
490 * @return the size of the wrapped buffer instance, expressed in bytes
491 */
492 protected abstract int getSize();
493 }
494
495 /**
496 * A BufferFactory is responsible for creating buffer and {@link BufferContainer} instances, and for returning the buffer
497 * Class. The Class returned by {@link #getBufferClass()} may be a superclass or superinterface of the actual
498 * objects returned by {@link #newBuffer(int)}.
499 */
500 public static abstract class BufferFactory {
501
502 /**
503 * Returns <code>true</code> if the class returned by {@link #getBufferClass()} is equal or a
504 * superclass/superinterface of the specified buffer class.
505 *
506 * @param bufferClass the buffer Class to test
507 * @return true if the class returned by <code>#getBufferClass()</code> is equal or a superclass/superinterface
508 * of the specified buffer class
509 */
510 public boolean matchesBufferClass(Class bufferClass) {
511 return getBufferClass().isAssignableFrom(bufferClass);
512 }
513
514 /**
515 * Creates and returns a buffer instance of the specified size.
516 *
517 * @param size size of the buffer to create
518 * @return a buffer instance of the specified size
519 */
520 public abstract Object newBuffer(int size);
521
522 /**
523 * Creates and returns a {@link BufferContainer} for the specified buffer instance.
524 *
525 * @param buffer the buffer to wrap in a BufferContainer
526 * @return returns a BufferContainer for the specified buffer instance
527 */
528 public abstract BufferContainer newBufferContainer(Object buffer);
529
530 /**
531 * Returns the Class of buffer instances this factory creates.
532 *
533 * @return the Class of buffer instances this factory creates
534 */
535 public abstract Class getBufferClass();
536 }
537
538 /**
539 * This class is a {@link BufferFactory} implementation for byte array (<code>byte[]</code>) buffers.
540 */
541 public static class ByteArrayFactory extends BufferFactory {
542 public Object newBuffer(int size) {
543 return new byte[size];
544 }
545
546 public BufferContainer newBufferContainer(Object buffer) {
547 return new BufferContainer(buffer) {
548 protected int getLength() {
549 return ((byte[])buffer).length;
550 }
551
552 protected int getSize() {
553 return getLength();
554 }
555 };
556 }
557
558 public Class getBufferClass() {
559 return byte[].class;
560 }
561 }
562
563 /**
564 * This class is a {@link BufferFactory} implementation for char array (<code>char[]</code>) buffers.
565 */
566 public static class CharArrayFactory extends BufferFactory {
567 public Object newBuffer(int size) {
568 return new char[size];
569 }
570
571 public BufferContainer newBufferContainer(Object buffer) {
572 return new BufferContainer(buffer) {
573 protected int getLength() {
574 return ((char[])buffer).length;
575 }
576
577 protected int getSize() {
578 return 2*getLength();
579 }
580 };
581 }
582
583 public Class getBufferClass() {
584 return char[].class;
585 }
586 }
587
588 /**
589 * This class is a {@link BufferFactory} implementation for <code>java.nio.ByteBuffer</code> buffers.
590 * ByteBuffer instances created by {@link #newBuffer(int)} are direct ; the actually Class of those instances may be actually
591 * be <code>java.nio.DirectByteBuffer</code> and not <code>java.nio.ByteBuffer</code> as returned by
592 * {@link #getBufferClass()}.
593 */
594 public static class ByteBufferFactory extends BufferFactory {
595 public Object newBuffer(int size) {
596 // Note: the returned instance is actually a java.nio.DirectByteBuffer, this is why it's important to
597 // compare classes using Class#isAssignableFrom(Class)
598 return ByteBuffer.allocateDirect(size);
599 }
600
601 public BufferContainer newBufferContainer(Object buffer) {
602 return new BufferContainer(buffer) {
603 protected int getLength() {
604 return ((ByteBuffer)buffer).capacity();
605 }
606
607 protected int getSize() {
608 return getLength();
609 }
610 };
611 }
612
613 public Class getBufferClass() {
614 return ByteBuffer.class;
615 }
616 }
617
618 /**
619 * This class is a {@link BufferFactory} implementation for <code>java.nio.CharBuffer</code> buffers.
620 */
621 public static class CharBufferFactory extends BufferFactory {
622 public Object newBuffer(int size) {
623 return CharBuffer.allocate(size);
624 }
625
626 public BufferContainer newBufferContainer(Object buffer) {
627 return new BufferContainer(buffer) {
628 protected int getLength() {
629 return ((CharBuffer)buffer).capacity();
630 }
631
632 protected int getSize() {
633 return 2*getLength();
634 }
635 };
636 }
637
638 public Class getBufferClass() {
639 return CharBuffer.class;
640 }
641 }
642 }