1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.collection.Buffer; 13 14 import hunt.Exceptions; 15 16 import std.conv; 17 import std.format; 18 19 abstract class Buffer 20 { 21 // Invariants: mark <= position <= limit <= capacity 22 private int _mark = -1; 23 private int _position = 0; 24 private int _limit; 25 private int _capacity; 26 27 // Creates a new buffer with the given mark, position, limit, and capacity, 28 // after checking invariants. 29 // 30 this(int mark, int pos, int lim, int cap) { // package-private 31 if (cap < 0) 32 throw new IllegalArgumentException("Negative capacity: " ~ to!string(cap)); 33 this._capacity = cap; 34 limit(lim); 35 position(pos); 36 if (mark >= 0) { 37 if (mark > pos) 38 throw new IllegalArgumentException("mark > position: (" 39 ~ to!string( mark) ~ " > " ~ to!string(pos) ~ ")"); 40 this._mark = mark; 41 } 42 } 43 44 /** 45 * Returns this buffer's capacity. 46 * 47 * @return The capacity of this buffer 48 */ 49 final int capacity() { 50 return _capacity; 51 } 52 53 /** 54 * Returns this buffer's position. 55 * 56 * @return The position of this buffer 57 */ 58 final int position() { 59 return _position; 60 } 61 62 /** 63 * Sets this buffer's position. If the mark is defined and larger than the 64 * new position then it is discarded. 65 * 66 * @param newPosition 67 * The new position value; must be non-negative 68 * and no larger than the current limit 69 * 70 * @return This buffer 71 * 72 * @throws IllegalArgumentException 73 * If the preconditions on <tt>newPosition</tt> do not hold 74 */ 75 final Buffer position(int newPosition) { 76 if ((newPosition > _limit) || (newPosition < 0)) 77 throw new IllegalArgumentException( 78 format("Out of range: %d is included in (0, %d)", newPosition, _limit)); 79 _position = newPosition; 80 if (_mark > _position) _mark = -1; 81 return this; 82 } 83 84 /** 85 * Returns this buffer's limit. 86 * 87 * @return The limit of this buffer 88 */ 89 final int limit() { 90 return _limit; 91 } 92 93 /** 94 * Sets this buffer's limit. If the position is larger than the new limit 95 * then it is set to the new limit. If the mark is defined and larger than 96 * the new limit then it is discarded. 97 * 98 * @param newLimit 99 * The new limit value; must be non-negative 100 * and no larger than this buffer's capacity 101 * 102 * @return This buffer 103 * 104 * @throws IllegalArgumentException 105 * If the preconditions on <tt>newLimit</tt> do not hold 106 */ 107 final Buffer limit(int newLimit) { 108 if ((newLimit > _capacity) || (newLimit < 0)) 109 throw new IllegalArgumentException(""); 110 _limit = newLimit; 111 if (_position > _limit) _position = _limit; 112 if (_mark > _limit) _mark = -1; 113 return this; 114 } 115 116 /** 117 * Sets this buffer's mark at its position. 118 * 119 * @return This buffer 120 */ 121 final Buffer mark() { 122 _mark = _position; 123 return this; 124 } 125 126 /** 127 * Resets this buffer's position to the previously-marked position. 128 * 129 * <p> Invoking this method neither changes nor discards the mark's 130 * value. </p> 131 * 132 * @return This buffer 133 * 134 * @throws InvalidMarkException 135 * If the mark has not been set 136 */ 137 final Buffer reset() { 138 int m = _mark; 139 if (m < 0) 140 throw new InvalidMarkException(""); 141 _position = m; 142 return this; 143 } 144 145 /** 146 * Clears this buffer. The position is set to zero, the limit is set to 147 * the capacity, and the mark is discarded. 148 * 149 * <p> Invoke this method before using a sequence of channel-read or 150 * <i>put</i> operations to fill this buffer. For example: 151 * 152 * <blockquote><pre> 153 * buf.clear(); // Prepare buffer for reading 154 * in.read(buf); // Read data</pre></blockquote> 155 * 156 * <p> This method does not actually erase the data in the buffer, but it 157 * is named as if it did because it will most often be used in situations 158 * in which that might as well be the case. </p> 159 * 160 * @return This buffer 161 */ 162 final Buffer clear() { 163 _position = 0; 164 _limit = _capacity; 165 _mark = -1; 166 return this; 167 } 168 169 /** 170 * Flips this buffer. The limit is set to the current position and then 171 * the position is set to zero. If the mark is defined then it is 172 * discarded. 173 * 174 * <p> After a sequence of channel-read or <i>put</i> operations, invoke 175 * this method to prepare for a sequence of channel-write or relative 176 * <i>get</i> operations. For example: 177 * 178 * <blockquote><pre> 179 * buf.put(magic); // Prepend header 180 * in.read(buf); // Read data into rest of buffer 181 * buf.flip(); // Flip buffer 182 * out.write(buf); // Write header + data to channel</pre></blockquote> 183 * 184 * <p> This method is often used in conjunction with the {@link 185 * java.nio.ByteBuffer#compact compact} method when transferring data from 186 * one place to another. </p> 187 * 188 * @return This buffer 189 */ 190 final Buffer flip() { 191 _limit = _position; 192 _position = 0; 193 _mark = -1; 194 return this; 195 } 196 197 /** 198 * Rewinds this buffer. The position is set to zero and the mark is 199 * discarded. 200 * 201 * <p> Invoke this method before a sequence of channel-write or <i>get</i> 202 * operations, assuming that the limit has already been set 203 * appropriately. For example: 204 * 205 * <blockquote><pre> 206 * out.write(buf); // Write remaining data 207 * buf.rewind(); // Rewind buffer 208 * buf.get(array); // Copy data into array</pre></blockquote> 209 * 210 * @return This buffer 211 */ 212 final Buffer rewind() { 213 _position = 0; 214 _mark = -1; 215 return this; 216 } 217 218 /** 219 * Returns the number of elements between the current position and the 220 * limit. 221 * 222 * @return The number of elements remaining in this buffer 223 */ 224 final int remaining() { 225 return _limit - _position; 226 } 227 228 /** 229 * Tells whether there are any elements between the current position and 230 * the limit. 231 * 232 * @return <tt>true</tt> if, and only if, there is at least one element 233 * remaining in this buffer 234 */ 235 final bool hasRemaining() { 236 return _position < _limit; 237 } 238 239 /** 240 * Tells whether or not this buffer is read-only. 241 * 242 * @return <tt>true</tt> if, and only if, this buffer is read-only 243 */ 244 abstract bool isReadOnly(); 245 246 /** 247 * Tells whether or not this buffer is backed by an accessible 248 * array. 249 * 250 * <p> If this method returns <tt>true</tt> then the {@link #array() array} 251 * and {@link #arrayOffset() arrayOffset} methods may safely be invoked. 252 * </p> 253 * 254 * @return <tt>true</tt> if, and only if, this buffer 255 * is backed by an array and is not read-only 256 * 257 */ 258 abstract bool hasArray(); 259 260 /** 261 * Returns the array that backs this 262 * buffer <i>(optional operation)</i>. 263 * 264 * <p> This method is intended to allow array-backed buffers to be 265 * passed to native code more efficiently. Concrete subclasses 266 * provide more strongly-typed return values for this method. 267 * 268 * <p> Modifications to this buffer's content will cause the returned 269 * array's content to be modified, and vice versa. 270 * 271 * <p> Invoke the {@link #hasArray hasArray} method before invoking this 272 * method in order to ensure that this buffer has an accessible backing 273 * array. </p> 274 * 275 * @return The array that backs this buffer 276 * 277 * @throws ReadOnlyBufferException 278 * If this buffer is backed by an array but is read-only 279 * 280 * @throws UnsupportedOperationException 281 * If this buffer is not backed by an accessible array 282 * 283 */ 284 // abstract Object array(); 285 286 /** 287 * Returns the offset within this buffer's backing array of the first 288 * element of the buffer <i>(optional operation)</i>. 289 * 290 * <p> If this buffer is backed by an array then buffer position <i>p</i> 291 * corresponds to array index <i>p</i> + <tt>arrayOffset()</tt>. 292 * 293 * <p> Invoke the {@link #hasArray hasArray} method before invoking this 294 * method in order to ensure that this buffer has an accessible backing 295 * array. </p> 296 * 297 * @return The offset within this buffer's array 298 * of the first element of the buffer 299 * 300 * @throws ReadOnlyBufferException 301 * If this buffer is backed by an array but is read-only 302 * 303 * @throws UnsupportedOperationException 304 * If this buffer is not backed by an accessible array 305 * 306 */ 307 abstract int arrayOffset(); 308 309 /** 310 * Tells whether or not this buffer is 311 * <a href="ByteBuffer.html#direct"><i>direct</i></a>. 312 * 313 * @return <tt>true</tt> if, and only if, this buffer is direct 314 * 315 */ 316 abstract bool isDirect(); 317 318 319 // -- Package-private methods for bounds checking, etc. -- 320 321 /** 322 * Checks the current position against the limit, throwing a {@link 323 * BufferUnderflowException} if it is not smaller than the limit, and then 324 * increments the position. 325 * 326 * @return The current position value, before it is incremented 327 */ 328 final int nextGetIndex() { // package-private 329 if (_position >= _limit) 330 throw new BufferUnderflowException(format("limit=%d, position=%d", _limit, _position)); 331 return _position++; 332 } 333 334 final int nextGetIndex(int nb) { // package-private 335 if (_limit - _position < nb) 336 throw new BufferUnderflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb)); 337 int p = _position; 338 _position += nb; 339 return p; 340 } 341 342 /** 343 * Checks the current position against the limit, throwing a {@link 344 * BufferOverflowException} if it is not smaller than the limit, and then 345 * increments the position. 346 * 347 * @return The current position value, before it is incremented 348 */ 349 final int nextPutIndex() { // package-private 350 if (_position >= _limit) 351 throw new BufferOverflowException(format("limit=%d, position=%d", _limit, _position)); 352 return _position++; 353 } 354 355 final int nextPutIndex(int nb) { // package-private 356 if (_limit - _position < nb) 357 throw new BufferOverflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb)); 358 int p = _position; 359 _position += nb; 360 return p; 361 } 362 363 /** 364 * Checks the given index against the limit, throwing an {@link 365 * IndexOutOfBoundsException} if it is not smaller than the limit 366 * or is smaller than zero. 367 */ 368 final int checkIndex(int i) { // package-private 369 if ((i < 0) || (i >= _limit)) 370 throw new IndexOutOfBoundsException("Out of range"); 371 return i; 372 } 373 374 final int checkIndex(int i, int nb) { // package-private 375 if ((i < 0) || (nb > _limit - i)) 376 throw new IndexOutOfBoundsException("Out of range"); 377 return i; 378 } 379 380 final int markValue() { // package-private 381 return _mark; 382 } 383 384 final void truncate() { // package-private 385 _mark = -1; 386 _position = 0; 387 _limit = 0; 388 _capacity = 0; 389 } 390 391 final void discardMark() { // package-private 392 _mark = -1; 393 } 394 395 static void checkBounds(int off, int len, int size) { // package-private 396 if ((off | len | (off + len) | (size - (off + len))) < 0) 397 throw new IndexOutOfBoundsException("Out of range"); 398 } 399 }