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}