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    public void unlockBuffer() throws IllegalStateException {
159        int locks = lockCount.decrementAndGet();
160        if (locks < 0) {
161            throw new IllegalStateException(
162                "Buffer not locked or released already.");
163        }
164        if (locks == 0) {
165            if (linkedTo != null) {
166                backing = savedBacking;
167                linkedTo.unlockBuffer();
168                linkedTo = null;
169            }
170            manager.recollect(this);
171        }
172    }
173
174    /**
175     * Convenience method to fill the buffer from the channel.
176     * Unlocks the buffer if an {@link IOException} occurs.
177     * This method may only be invoked for {@link ManagedBuffer}s
178     * backed by a {@link ByteBuffer}.
179     *
180     * @param channel the channel
181     * @return the bytes read
182     * @throws IOException Signals that an I/O exception has occurred.
183     */
184    public int fillFromChannel(
185            ReadableByteChannel channel) throws IOException {
186        if (!(backing instanceof ByteBuffer)) {
187            throw new IllegalArgumentException(
188                "Backing buffer is not a ByteBuffer.");
189        }
190        try {
191            return channel.read((ByteBuffer) backing);
192        } catch (IOException e) {
193            unlockBuffer();
194            throw e;
195        }
196    }
197
198    /*
199     * (non-Javadoc)
200     * 
201     * @see java.lang.Object#toString()
202     */
203    @Override
204    public String toString() {
205        StringBuilder builder = new StringBuilder();
206        builder.append(getClass().getSimpleName())
207            .append(" [");
208        if (backing != null) {
209            builder.append("buffer=");
210            builder.append(backing);
211            builder.append(", ");
212        }
213        if (lockCount != null) {
214            builder.append("lockCount=");
215            builder.append(lockCount);
216        }
217        builder.append(']');
218        return builder.toString();
219    }
220
221    /**
222     * @return the backing array
223     * @see java.nio.Buffer#array()
224     */
225    public Object array() {
226        return backing.array();
227    }
228
229    /**
230     * @return the backing array offset
231     * @see java.nio.Buffer#arrayOffset()
232     */
233    public int arrayOffset() {
234        return backing.arrayOffset();
235    }
236
237    /**
238     * @return the capacity
239     * @see java.nio.Buffer#capacity()
240     */
241    public final int capacity() {
242        return backing.capacity();
243    }
244
245    /**
246     * @return the buffer
247     * @see java.nio.Buffer#clear()
248     */
249    public final Buffer clear() {
250        return backing.clear();
251    }
252
253    /**
254     * Duplicate the buffer.
255     *
256     * @return the t
257     */
258    @SuppressWarnings("unchecked")
259    public final T duplicate() {
260        if (backing instanceof ByteBuffer) {
261            return (T) ((ByteBuffer) backing).duplicate();
262        }
263        if (backing instanceof CharBuffer) {
264            return (T) ((CharBuffer) backing).duplicate();
265        }
266        throw new IllegalArgumentException("Backing buffer of unknown type.");
267    }
268
269    /**
270     * @return the buffer
271     * @see java.nio.Buffer#flip()
272     */
273    public final Buffer flip() {
274        return backing.flip();
275    }
276
277    /**
278     * @return the result
279     * @see java.nio.Buffer#hasArray()
280     */
281    public boolean hasArray() {
282        return backing.hasArray();
283    }
284
285    /**
286     * @return the result
287     * @see java.nio.Buffer#hasRemaining()
288     */
289    public final boolean hasRemaining() {
290        return backing.hasRemaining();
291    }
292
293    /**
294     * @return the result
295     * @see java.nio.Buffer#isDirect()
296     */
297    public boolean isDirect() {
298        return backing.isDirect();
299    }
300
301    /**
302     * @return the result
303     * @see java.nio.Buffer#isReadOnly()
304     */
305    public boolean isReadOnly() {
306        return backing.isReadOnly();
307    }
308
309    /**
310     * @return the result
311     * @see java.nio.Buffer#limit()
312     */
313    public final int limit() {
314        return backing.limit();
315    }
316
317    /**
318     * @param newLimit the new limit
319     * @return the result
320     * @see java.nio.Buffer#limit(int)
321     */
322    public final Buffer limit(int newLimit) {
323        return backing.limit(newLimit);
324    }
325
326    /**
327     * @return the buffer
328     * @see java.nio.Buffer#mark()
329     */
330    public final Buffer mark() {
331        return backing.mark();
332    }
333
334    /**
335     * @return the result
336     * @see java.nio.Buffer#position()
337     */
338    public final int position() {
339        return backing.position();
340    }
341
342    /**
343     * @param newPosition the new position
344     * @return the buffer
345     * @see java.nio.Buffer#position(int)
346     */
347    public final Buffer position(int newPosition) {
348        return backing.position(newPosition);
349    }
350
351    /**
352     * @return the result
353     * @see java.nio.Buffer#remaining()
354     */
355    public final int remaining() {
356        return backing.remaining();
357    }
358
359    /**
360     * @return the Buffer
361     * @see java.nio.Buffer#reset()
362     */
363    public final Buffer reset() {
364        return backing.reset();
365    }
366
367    /**
368     * @return the Buffer
369     * @see java.nio.Buffer#rewind()
370     */
371    public final Buffer rewind() {
372        return backing.rewind();
373    }
374
375    /**
376     * Creates a new {@link ByteBuffer} view.
377     *
378     * @return the byte buffer view
379     */
380    @SuppressWarnings("PMD.AccessorClassGeneration")
381    public ByteBufferView newByteBufferView() {
382        return new ByteBufferView();
383    }
384
385    /**
386     * A read-only view of the managed buffer's content
387     * (backing buffer) and a reference to the managed buffer.
388     * Can be used if several consumers need the same content.
389     */
390    public class ByteBufferView {
391        private final ByteBuffer bufferView;
392
393        private ByteBufferView() {
394            if (!(backing instanceof ByteBuffer)) {
395                throw new IllegalArgumentException("Not a managed ByteBuffer.");
396            }
397            bufferView = ((ByteBuffer) backing).asReadOnlyBuffer();
398        }
399
400        /**
401         * Returns the {@link ByteBuffer} that represents this
402         * view (position, mark, limit).
403         * 
404         * @return the `ByteBuffer` view
405         */
406        public ByteBuffer get() {
407            return bufferView;
408        }
409
410        /**
411         * Returns the managed buffer that this reader is a view of.
412         * 
413         * @return the managed buffer
414         */
415        @SuppressWarnings("unchecked")
416        public ManagedBuffer<ByteBuffer> managedBuffer() {
417            return (ManagedBuffer<ByteBuffer>) ManagedBuffer.this;
418        }
419    }
420
421    /**
422     * Creates a new {@link CharBuffer} view.
423     *
424     * @return the byte buffer view
425     */
426    @SuppressWarnings("PMD.AccessorClassGeneration")
427    public CharBufferView newCharBufferView() {
428        return new CharBufferView();
429    }
430
431    /**
432     * A read-only view of the managed buffer's content
433     * (backing buffer) and a reference to the managed buffer.
434     * Can be used if several consumers need the same content.
435     */
436    public class CharBufferView {
437        private final CharBuffer bufferView;
438
439        private CharBufferView() {
440            if (!(backing instanceof CharBuffer)) {
441                throw new IllegalArgumentException("Not a managed CharBuffer.");
442            }
443            bufferView = ((CharBuffer) backing).asReadOnlyBuffer();
444        }
445
446        /**
447         * Returns the {@link ByteBuffer} that represents this
448         * view (position, mark, limit).
449         * 
450         * @return the `ByteBuffer` view
451         */
452        public CharBuffer get() {
453            return bufferView;
454        }
455
456        /**
457         * Returns the managed buffer that this reader is a view of.
458         * 
459         * @return the managed buffer
460         */
461        @SuppressWarnings("unchecked")
462        public ManagedBuffer<CharBuffer> managedBuffer() {
463            return (ManagedBuffer<CharBuffer>) ManagedBuffer.this;
464        }
465    }
466}