001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero 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 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.io.util;
020
021import java.io.IOException;
022import java.nio.Buffer;
023import java.nio.ByteBuffer;
024import java.nio.CharBuffer;
025import java.nio.channels.ReadableByteChannel;
026import java.util.concurrent.atomic.AtomicInteger;
027
028/**
029 * A wrapper around a {@link Buffer} that maintains a lock count for that
030 * buffer. All methods known from {@code Buffer} are provided and
031 * delegate to the backing buffer. Managed buffers can be used to maintain
032 * pools of buffers. Buffers are locked when retrieved from the pool
033 * and can automatically be returned when the last lock is released.
034 * 
035 * Newly created managed buffer always have a lock count of 1 (you
036 * create them for using them, don't you).
037 */
038@SuppressWarnings("PMD.TooManyMethods")
039public class ManagedBuffer<T extends Buffer> {
040
041    public static final ManagedBuffer<ByteBuffer> EMPTY_BYTE_BUFFER
042        = wrap(ByteBuffer.allocate(0));
043
044    public static final ManagedBuffer<CharBuffer> EMPTY_CHAR_BUFFER
045        = wrap(CharBuffer.allocate(0));
046
047    protected T backing;
048    private ManagedBuffer<T> linkedTo;
049    protected T savedBacking;
050    private final BufferCollector<ManagedBuffer<T>> manager;
051    private final AtomicInteger lockCount = new AtomicInteger(1);
052
053    /**
054     * Create a new Managed buffer, backed by the given buffer,
055     * with a lock count of one.
056     * 
057     * @param buffer the backing buffer
058     * @param manager used for restoring the buffer when the lock 
059     * count reaches zero
060     */
061    public ManagedBuffer(T buffer, BufferCollector<ManagedBuffer<T>> manager) {
062        this.backing = buffer;
063        this.manager = manager;
064    }
065
066    /**
067     * Convenience method for creating a {@link ManagedBuffer} with
068     * a {@link BufferCollector#NOOP_COLLECTOR} from a NIO buffer.
069     * Effectively, this creates an *unmanaged* buffer that
070     * looks like a managed buffer from an existing NIO buffer that
071     * does not belong to any pool.
072     *
073     * @param <B> the buffer type
074     * @param buffer the buffer to wrap
075     * @return the managed buffer
076     */
077    public static <B extends Buffer> ManagedBuffer<B> wrap(B buffer) {
078        return new ManagedBuffer<B>(buffer, BufferCollector.noopCollector());
079    }
080
081    /**
082     * Return the backing buffer.
083     * 
084     * @return the buffer
085     */
086    public T backingBuffer() {
087        return backing;
088    }
089
090    /**
091     * Replace the backing buffer.
092     * 
093     * @param buffer the new buffer
094     * @return the managed buffer for easy chaining
095     */
096    public ManagedBuffer<T> replaceBackingBuffer(T buffer) {
097        backing = buffer;
098        return this;
099    }
100
101    /**
102     * Links this instance's backing buffer (temporarily) to
103     * the given buffer's backing buffer. Locks the given buffer.
104     * The buffer is unlocked again and the original backing buffer
105     * restored if this method is called with `null` or this
106     * managed buffer is recollected (i.e. no longer used). 
107     *
108     * This method may be used to "assign" data that is already
109     * available in a buffer to this buffer without copying it
110     * over. 
111     *
112     * @param buffer the buffer
113     * @return the managed buffer for easy chaining
114     */
115    public ManagedBuffer<T> linkBackingBuffer(ManagedBuffer<T> buffer) {
116        if (linkedTo != null) {
117            backing = savedBacking;
118            linkedTo.unlockBuffer();
119            linkedTo = null;
120        }
121        if (buffer == null) {
122            return this;
123        }
124        buffer.lockBuffer();
125        linkedTo = buffer;
126        savedBacking = backing;
127        backing = linkedTo.backing;
128        return this;
129    }
130
131    /**
132     * Return the buffer's manager.
133     * 
134     * @return the manager
135     */
136    public BufferCollector<?> manager() {
137        return manager;
138    }
139
140    /**
141     * Increases the buffer's lock count.
142     * 
143     * @return the managed buffer for easy chaining
144     */
145    public ManagedBuffer<T> lockBuffer() {
146        lockCount.incrementAndGet();
147        return this;
148    }
149
150    /**
151     * Decreases the buffer's lock count. If the lock count reached
152     * zero, the buffer collect's {@link BufferCollector#recollect}
153     * method is invoked. 
154     * 
155     * @throws IllegalStateException if the buffer is not locked or 
156     * has been released already
157     */
158    @SuppressWarnings("PMD.AvoidUncheckedExceptionsInSignatures")
159    public void unlockBuffer() throws IllegalStateException {
160        int locks = lockCount.decrementAndGet();
161        if (locks < 0) {
162            throw new IllegalStateException(
163                "Buffer not locked or released already.");
164        }
165        if (locks == 0) {
166            if (linkedTo != null) {
167                backing = savedBacking;
168                linkedTo.unlockBuffer();
169                linkedTo = null;
170            }
171            manager.recollect(this);
172        }
173    }
174
175    /**
176     * Convenience method to fill the buffer from the channel.
177     * Unlocks the buffer if an {@link IOException} occurs.
178     * This method may only be invoked for {@link ManagedBuffer}s
179     * backed by a {@link ByteBuffer}.
180     *
181     * @param channel the channel
182     * @return the bytes read
183     * @throws IOException Signals that an I/O exception has occurred.
184     */
185    public int fillFromChannel(
186            ReadableByteChannel channel) throws IOException {
187        if (!(backing instanceof ByteBuffer)) {
188            throw new IllegalArgumentException(
189                "Backing buffer is not a ByteBuffer.");
190        }
191        try {
192            return channel.read((ByteBuffer) backing);
193        } catch (IOException e) {
194            unlockBuffer();
195            throw e;
196        }
197    }
198
199    /*
200     * (non-Javadoc)
201     * 
202     * @see java.lang.Object#toString()
203     */
204    @Override
205    public String toString() {
206        StringBuilder builder = new StringBuilder(50);
207        builder.append(getClass().getSimpleName())
208            .append(" [");
209        if (backing != null) {
210            builder.append("buffer=").append(backing).append(", ");
211        }
212        if (lockCount != null) {
213            builder.append("lockCount=").append(lockCount);
214        }
215        builder.append(']');
216        return builder.toString();
217    }
218
219    /**
220     * @return the backing array
221     * @see java.nio.Buffer#array()
222     */
223    public Object array() {
224        return backing.array();
225    }
226
227    /**
228     * @return the backing array offset
229     * @see java.nio.Buffer#arrayOffset()
230     */
231    public int arrayOffset() {
232        return backing.arrayOffset();
233    }
234
235    /**
236     * @return the capacity
237     * @see java.nio.Buffer#capacity()
238     */
239    public final int capacity() {
240        return backing.capacity();
241    }
242
243    /**
244     * @return the buffer
245     * @see java.nio.Buffer#clear()
246     */
247    public final Buffer clear() {
248        return backing.clear();
249    }
250
251    /**
252     * Duplicate the buffer.
253     *
254     * @return the t
255     */
256    @SuppressWarnings("unchecked")
257    public final T duplicate() {
258        if (backing instanceof ByteBuffer) {
259            return (T) ((ByteBuffer) backing).duplicate();
260        }
261        if (backing instanceof CharBuffer) {
262            return (T) ((CharBuffer) backing).duplicate();
263        }
264        throw new IllegalArgumentException("Backing buffer of unknown type.");
265    }
266
267    /**
268     * @return the buffer
269     * @see java.nio.Buffer#flip()
270     */
271    public final Buffer flip() {
272        return backing.flip();
273    }
274
275    /**
276     * @return the result
277     * @see java.nio.Buffer#hasArray()
278     */
279    public boolean hasArray() {
280        return backing.hasArray();
281    }
282
283    /**
284     * @return the result
285     * @see java.nio.Buffer#hasRemaining()
286     */
287    public final boolean hasRemaining() {
288        return backing.hasRemaining();
289    }
290
291    /**
292     * @return the result
293     * @see java.nio.Buffer#isDirect()
294     */
295    public boolean isDirect() {
296        return backing.isDirect();
297    }
298
299    /**
300     * @return the result
301     * @see java.nio.Buffer#isReadOnly()
302     */
303    public boolean isReadOnly() {
304        return backing.isReadOnly();
305    }
306
307    /**
308     * @return the result
309     * @see java.nio.Buffer#limit()
310     */
311    public final int limit() {
312        return backing.limit();
313    }
314
315    /**
316     * @param newLimit the new limit
317     * @return the result
318     * @see java.nio.Buffer#limit(int)
319     */
320    public final Buffer limit(int newLimit) {
321        return backing.limit(newLimit);
322    }
323
324    /**
325     * @return the buffer
326     * @see java.nio.Buffer#mark()
327     */
328    public final Buffer mark() {
329        return backing.mark();
330    }
331
332    /**
333     * @return the result
334     * @see java.nio.Buffer#position()
335     */
336    public final int position() {
337        return backing.position();
338    }
339
340    /**
341     * @param newPosition the new position
342     * @return the buffer
343     * @see java.nio.Buffer#position(int)
344     */
345    public final Buffer position(int newPosition) {
346        return backing.position(newPosition);
347    }
348
349    /**
350     * @return the result
351     * @see java.nio.Buffer#remaining()
352     */
353    public final int remaining() {
354        return backing.remaining();
355    }
356
357    /**
358     * @return the Buffer
359     * @see java.nio.Buffer#reset()
360     */
361    public final Buffer reset() {
362        return backing.reset();
363    }
364
365    /**
366     * @return the Buffer
367     * @see java.nio.Buffer#rewind()
368     */
369    public final Buffer rewind() {
370        return backing.rewind();
371    }
372
373    /**
374     * Creates a new {@link ByteBuffer} view.
375     *
376     * @return the byte buffer view
377     */
378    @SuppressWarnings("PMD.AccessorClassGeneration")
379    public ByteBufferView newByteBufferView() {
380        return new ByteBufferView();
381    }
382
383    /**
384     * A read-only view of the managed buffer's content
385     * (backing buffer) and a reference to the managed buffer.
386     * Can be used if several consumers need the same content.
387     */
388    public final class ByteBufferView {
389        private final ByteBuffer bufferView;
390
391        private ByteBufferView() {
392            if (!(backing instanceof ByteBuffer)) {
393                throw new IllegalArgumentException("Not a managed ByteBuffer.");
394            }
395            bufferView = ((ByteBuffer) backing).asReadOnlyBuffer();
396        }
397
398        /**
399         * Returns the {@link ByteBuffer} that represents this
400         * view (position, mark, limit).
401         * 
402         * @return the `ByteBuffer` view
403         */
404        public ByteBuffer get() {
405            return bufferView;
406        }
407
408        /**
409         * Returns the managed buffer that this reader is a view of.
410         * 
411         * @return the managed buffer
412         */
413        @SuppressWarnings("unchecked")
414        public ManagedBuffer<ByteBuffer> managedBuffer() {
415            return (ManagedBuffer<ByteBuffer>) ManagedBuffer.this;
416        }
417    }
418
419    /**
420     * Creates a new {@link CharBuffer} view.
421     *
422     * @return the byte buffer view
423     */
424    @SuppressWarnings("PMD.AccessorClassGeneration")
425    public CharBufferView newCharBufferView() {
426        return new CharBufferView();
427    }
428
429    /**
430     * A read-only view of the managed buffer's content
431     * (backing buffer) and a reference to the managed buffer.
432     * Can be used if several consumers need the same content.
433     */
434    public final class CharBufferView {
435        private final CharBuffer bufferView;
436
437        private CharBufferView() {
438            if (!(backing instanceof CharBuffer)) {
439                throw new IllegalArgumentException("Not a managed CharBuffer.");
440            }
441            bufferView = ((CharBuffer) backing).asReadOnlyBuffer();
442        }
443
444        /**
445         * Returns the {@link ByteBuffer} that represents this
446         * view (position, mark, limit).
447         * 
448         * @return the `ByteBuffer` view
449         */
450        public CharBuffer get() {
451            return bufferView;
452        }
453
454        /**
455         * Returns the managed buffer that this reader is a view of.
456         * 
457         * @return the managed buffer
458         */
459        @SuppressWarnings("unchecked")
460        public ManagedBuffer<CharBuffer> managedBuffer() {
461            return (ManagedBuffer<CharBuffer>) ManagedBuffer.this;
462        }
463    }
464}