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}