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.io.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 compact method when 185 * transferring data from one place to another. </p> 186 * 187 * @return This buffer 188 */ 189 final Buffer flip() { 190 _limit = _position; 191 _position = 0; 192 _mark = -1; 193 return this; 194 } 195 196 /** 197 * Rewinds this buffer. The position is set to zero and the mark is 198 * discarded. 199 * 200 * <p> Invoke this method before a sequence of channel-write or <i>get</i> 201 * operations, assuming that the limit has already been set 202 * appropriately. For example: 203 * 204 * <blockquote><pre> 205 * out.write(buf); // Write remaining data 206 * buf.rewind(); // Rewind buffer 207 * buf.get(array); // Copy data into array</pre></blockquote> 208 * 209 * @return This buffer 210 */ 211 final Buffer rewind() { 212 _position = 0; 213 _mark = -1; 214 return this; 215 } 216 217 /** 218 * Returns the number of elements between the current position and the 219 * limit. 220 * 221 * @return The number of elements remaining in this buffer 222 */ 223 final int remaining() { 224 return _limit - _position; 225 } 226 227 /** 228 * Tells whether there are any elements between the current position and 229 * the limit. 230 * 231 * @return <tt>true</tt> if, and only if, there is at least one element 232 * remaining in this buffer 233 */ 234 final bool hasRemaining() { 235 return _position < _limit; 236 } 237 238 /** 239 * Tells whether or not this buffer is read-only. 240 * 241 * @return <tt>true</tt> if, and only if, this buffer is read-only 242 */ 243 abstract bool isReadOnly(); 244 245 /** 246 * Tells whether or not this buffer is backed by an accessible 247 * array. 248 * 249 * <p> If this method returns <tt>true</tt> then the {@link #array() array} 250 * and {@link #arrayOffset() arrayOffset} methods may safely be invoked. 251 * </p> 252 * 253 * @return <tt>true</tt> if, and only if, this buffer 254 * is backed by an array and is not read-only 255 * 256 */ 257 abstract bool hasArray(); 258 259 /** 260 * Returns the array that backs this 261 * buffer <i>(optional operation)</i>. 262 * 263 * <p> This method is intended to allow array-backed buffers to be 264 * passed to native code more efficiently. Concrete subclasses 265 * provide more strongly-typed return values for this method. 266 * 267 * <p> Modifications to this buffer's content will cause the returned 268 * array's content to be modified, and vice versa. 269 * 270 * <p> Invoke the {@link #hasArray hasArray} method before invoking this 271 * method in order to ensure that this buffer has an accessible backing 272 * array. </p> 273 * 274 * @return The array that backs this buffer 275 * 276 * @throws ReadOnlyBufferException 277 * If this buffer is backed by an array but is read-only 278 * 279 * @throws UnsupportedOperationException 280 * If this buffer is not backed by an accessible array 281 * 282 */ 283 // abstract Object array(); 284 285 /** 286 * Returns the offset within this buffer's backing array of the first 287 * element of the buffer <i>(optional operation)</i>. 288 * 289 * <p> If this buffer is backed by an array then buffer position <i>p</i> 290 * corresponds to array index <i>p</i> + <tt>arrayOffset()</tt>. 291 * 292 * <p> Invoke the {@link #hasArray hasArray} method before invoking this 293 * method in order to ensure that this buffer has an accessible backing 294 * array. </p> 295 * 296 * @return The offset within this buffer's array 297 * of the first element of the buffer 298 * 299 * @throws ReadOnlyBufferException 300 * If this buffer is backed by an array but is read-only 301 * 302 * @throws UnsupportedOperationException 303 * If this buffer is not backed by an accessible array 304 * 305 */ 306 abstract int arrayOffset(); 307 308 /** 309 * Tells whether or not this buffer is 310 * <a href="ByteBuffer.html#direct"><i>direct</i></a>. 311 * 312 * @return <tt>true</tt> if, and only if, this buffer is direct 313 * 314 */ 315 abstract bool isDirect(); 316 317 318 // -- Package-private methods for bounds checking, etc. -- 319 320 /** 321 * Checks the current position against the limit, throwing a {@link 322 * BufferUnderflowException} if it is not smaller than the limit, and then 323 * increments the position. 324 * 325 * @return The current position value, before it is incremented 326 */ 327 final int nextGetIndex() { // package-private 328 if (_position >= _limit) 329 throw new BufferUnderflowException(format("limit=%d, position=%d", _limit, _position)); 330 return _position++; 331 } 332 333 final int nextGetIndex(int nb) { // package-private 334 if (_limit - _position < nb) 335 throw new BufferUnderflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb)); 336 int p = _position; 337 _position += nb; 338 return p; 339 } 340 341 /** 342 * Checks the current position against the limit, throwing a {@link 343 * BufferOverflowException} if it is not smaller than the limit, and then 344 * increments the position. 345 * 346 * @return The current position value, before it is incremented 347 */ 348 final int nextPutIndex() { // package-private 349 if (_position >= _limit) 350 throw new BufferOverflowException(format("limit=%d, position=%d", _limit, _position)); 351 return _position++; 352 } 353 354 final int nextPutIndex(int nb) { // package-private 355 if (_limit - _position < nb) 356 throw new BufferOverflowException(format("limit=%d, position=%d, nb=%d", _limit, _position, nb)); 357 int p = _position; 358 _position += nb; 359 return p; 360 } 361 362 /** 363 * Checks the given index against the limit, throwing an {@link 364 * IndexOutOfBoundsException} if it is not smaller than the limit 365 * or is smaller than zero. 366 */ 367 final int checkIndex(int i) { // package-private 368 if ((i < 0) || (i >= _limit)) 369 throw new IndexOutOfBoundsException("Out of range"); 370 return i; 371 } 372 373 final int checkIndex(int i, int nb) { // package-private 374 if ((i < 0) || (nb > _limit - i)) 375 throw new IndexOutOfBoundsException("Out of range"); 376 return i; 377 } 378 379 final int markValue() { // package-private 380 return _mark; 381 } 382 383 final void truncate() { // package-private 384 _mark = -1; 385 _position = 0; 386 _limit = 0; 387 _capacity = 0; 388 } 389 390 final void discardMark() { // package-private 391 _mark = -1; 392 } 393 394 static void checkBounds(int off, int len, int size) { // package-private 395 if ((off | len | (off + len) | (size - (off + len))) < 0) 396 throw new IndexOutOfBoundsException("Out of range"); 397 } 398 }