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();
207        builder.append(getClass().getSimpleName())
208            .append(" [");
209        if (backing != null) {
210            builder.append("buffer=");
211            builder.append(backing);
212            builder.append(", ");
213        }
214        if (lockCount != null) {
215            builder.append("lockCount=");
216            builder.append(lockCount);
217        }
218        builder.append(']');
219        return builder.toString();
220    }
221
222    /**
223     * @return the backing array
224     * @see java.nio.Buffer#array()
225     */
226    public Object array() {
227        return backing.array();
228    }
229
230    /**
231     * @return the backing array offset
232     * @see java.nio.Buffer#arrayOffset()
233     */
234    public int arrayOffset() {
235        return backing.arrayOffset();
236    }
237
238    /**
239     * @return the capacity
240     * @see java.nio.Buffer#capacity()
241     */
242    public final int capacity() {
243        return backing.capacity();
244    }
245
246    /**
247     * @return the buffer
248     * @see java.nio.Buffer#clear()
249     */
250    public final Buffer clear() {
251        return backing.clear();
252    }
253
254    /**
255     * Duplicate the buffer.
256     *
257     * @return the t
258     */
259    @SuppressWarnings("unchecked")
260    public final T duplicate() {
261        if (backing instanceof ByteBuffer) {
262            return (T) ((ByteBuffer) backing).duplicate();
263        }
264        if (backing instanceof CharBuffer) {
265            return (T) ((CharBuffer) backing).duplicate();
266        }
267        throw new IllegalArgumentException("Backing buffer of unknown type.");
268    }
269
270    /**
271     * @return the buffer
272     * @see java.nio.Buffer#flip()
273     */
274    public final Buffer flip() {
275        return backing.flip();
276    }
277
278    /**
279     * @return the result
280     * @see java.nio.Buffer#hasArray()
281     */
282    public boolean hasArray() {
283        return backing.hasArray();
284    }
285
286    /**
287     * @return the result
288     * @see java.nio.Buffer#hasRemaining()
289     */
290    public final boolean hasRemaining() {
291        return backing.hasRemaining();
292    }
293
294    /**
295     * @return the result
296     * @see java.nio.Buffer#isDirect()
297     */
298    public boolean isDirect() {
299        return backing.isDirect();
300    }
301
302    /**
303     * @return the result
304     * @see java.nio.Buffer#isReadOnly()
305     */
306    public boolean isReadOnly() {
307        return backing.isReadOnly();
308    }
309
310    /**
311     * @return the result
312     * @see java.nio.Buffer#limit()
313     */
314    public final int limit() {
315        return backing.limit();
316    }
317
318    /**
319     * @param newLimit the new limit
320     * @return the result
321     * @see java.nio.Buffer#limit(int)
322     */
323    public final Buffer limit(int newLimit) {
324        return backing.limit(newLimit);
325    }
326
327    /**
328     * @return the buffer
329     * @see java.nio.Buffer#mark()
330     */
331    public final Buffer mark() {
332        return backing.mark();
333    }
334
335    /**
336     * @return the result
337     * @see java.nio.Buffer#position()
338     */
339    public final int position() {
340        return backing.position();
341    }
342
343    /**
344     * @param newPosition the new position
345     * @return the buffer
346     * @see java.nio.Buffer#position(int)
347     */
348    public final Buffer position(int newPosition) {
349        return backing.position(newPosition);
350    }
351
352    /**
353     * @return the result
354     * @see java.nio.Buffer#remaining()
355     */
356    public final int remaining() {
357        return backing.remaining();
358    }
359
360    /**
361     * @return the Buffer
362     * @see java.nio.Buffer#reset()
363     */
364    public final Buffer reset() {
365        return backing.reset();
366    }
367
368    /**
369     * @return the Buffer
370     * @see java.nio.Buffer#rewind()
371     */
372    public final Buffer rewind() {
373        return backing.rewind();
374    }
375
376    /**
377     * Creates a new {@link ByteBuffer} view.
378     *
379     * @return the byte buffer view
380     */
381    @SuppressWarnings("PMD.AccessorClassGeneration")
382    public ByteBufferView newByteBufferView() {
383        return new ByteBufferView();
384    }
385
386    /**
387     * A read-only view of the managed buffer's content
388     * (backing buffer) and a reference to the managed buffer.
389     * Can be used if several consumers need the same content.
390     */
391    public class ByteBufferView {
392        private final ByteBuffer bufferView;
393
394        private ByteBufferView() {
395            if (!(backing instanceof ByteBuffer)) {
396                throw new IllegalArgumentException("Not a managed ByteBuffer.");
397            }
398            bufferView = ((ByteBuffer) backing).asReadOnlyBuffer();
399        }
400
401        /**
402         * Returns the {@link ByteBuffer} that represents this
403         * view (position, mark, limit).
404         * 
405         * @return the `ByteBuffer` view
406         */
407        public ByteBuffer get() {
408            return bufferView;
409        }
410
411        /**
412         * Returns the managed buffer that this reader is a view of.
413         * 
414         * @return the managed buffer
415         */
416        @SuppressWarnings("unchecked")
417        public ManagedBuffer<ByteBuffer> managedBuffer() {
418            return (ManagedBuffer<ByteBuffer>) ManagedBuffer.this;
419        }
420    }
421
422    /**
423     * Creates a new {@link CharBuffer} view.
424     *
425     * @return the byte buffer view
426     */
427    @SuppressWarnings("PMD.AccessorClassGeneration")
428    public CharBufferView newCharBufferView() {
429        return new CharBufferView();
430    }
431
432    /**
433     * A read-only view of the managed buffer's content
434     * (backing buffer) and a reference to the managed buffer.
435     * Can be used if several consumers need the same content.
436     */
437    public class CharBufferView {
438        private final CharBuffer bufferView;
439
440        private CharBufferView() {
441            if (!(backing instanceof CharBuffer)) {
442                throw new IllegalArgumentException("Not a managed CharBuffer.");
443            }
444            bufferView = ((CharBuffer) backing).asReadOnlyBuffer();
445        }
446
447        /**
448         * Returns the {@link ByteBuffer} that represents this
449         * view (position, mark, limit).
450         * 
451         * @return the `ByteBuffer` view
452         */
453        public CharBuffer get() {
454            return bufferView;
455        }
456
457        /**
458         * Returns the managed buffer that this reader is a view of.
459         * 
460         * @return the managed buffer
461         */
462        @SuppressWarnings("unchecked")
463        public ManagedBuffer<CharBuffer> managedBuffer() {
464            return (ManagedBuffer<CharBuffer>) ManagedBuffer.this;
465        }
466    }
467}