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.stream.Writer; 13 14 import hunt.Exceptions; 15 import hunt.util.Common; 16 import hunt.util.Appendable; 17 18 import std.algorithm; 19 import std.array; 20 import std.exception; 21 import std.conv; 22 import std.string; 23 24 /** 25 */ 26 class Writer : Appendable, Closeable, Flushable { 27 28 /** 29 * Temporary buffer used to hold writes of strings and single characters 30 */ 31 private byte[] writeBuffer; 32 33 /** 34 * Size of writeBuffer, must be >= 1 35 */ 36 private enum int WRITE_BUFFER_SIZE = 1024; 37 38 39 /** 40 * The object used to synchronize operations on this stream. For 41 * efficiency, a character-stream object may use an object other than 42 * itself to protect critical sections. A subclass should therefore use 43 * the object in this field rather than {@code this} or a synchronized 44 * method. 45 */ 46 protected Object lock; 47 48 /** 49 * Creates a new character-stream writer whose critical sections will 50 * synchronize on the writer itself. 51 */ 52 protected this() { 53 this.lock = this; 54 } 55 56 /** 57 * Creates a new character-stream writer whose critical sections will 58 * synchronize on the given object. 59 * 60 * @param lock 61 * Object to synchronize on 62 */ 63 protected this(Object lock) { 64 if (lock is null) { 65 throw new NullPointerException(); 66 } 67 this.lock = lock; 68 } 69 70 /** 71 * Writes a single character. The character to be written is contained in 72 * the 16 low-order bits of the given integer value; the 16 high-order bits 73 * are ignored. 74 * 75 * <p> Subclasses that intend to support efficient single-character output 76 * should override this method. 77 * 78 * @param c 79 * int specifying a character to be written 80 * 81 * @throws IOException 82 * If an I/O error occurs 83 */ 84 void write(int c) { 85 synchronized (lock) { 86 write([cast(byte) c]); 87 } 88 } 89 90 /** 91 * Writes an array of characters. 92 * 93 * @param cbuf 94 * Array of characters to be written 95 * 96 * @throws IOException 97 * If an I/O error occurs 98 */ 99 void write(byte[] cbuf) { 100 write(cbuf, 0, cast(int)cbuf.length); 101 } 102 103 /** 104 * Writes a portion of an array of characters. 105 * 106 * @param cbuf 107 * Array of characters 108 * 109 * @param off 110 * Offset from which to start writing characters 111 * 112 * @param len 113 * Number of characters to write 114 * 115 * @throws IndexOutOfBoundsException 116 * Implementations should throw this exception 117 * if {@code off} is negative, or {@code len} is negative, 118 * or {@code off + len} is negative or greater than the length 119 * of the given array 120 * 121 * @throws IOException 122 * If an I/O error occurs 123 */ 124 abstract void write(byte[] cbuf, int off, int len); 125 126 /** 127 * Writes a string. 128 * 129 * @param str 130 * string to be written 131 * 132 * @throws IOException 133 * If an I/O error occurs 134 */ 135 void write(string str) { 136 write(cast(byte[])str); 137 } 138 139 /** 140 * Writes a portion of a string. 141 * 142 * @implSpec 143 * The implementation in this class throws an 144 * {@code IndexOutOfBoundsException} for the indicated conditions; 145 * overriding methods may choose to do otherwise. 146 * 147 * @param str 148 * A string 149 * 150 * @param off 151 * Offset from which to start writing characters 152 * 153 * @param len 154 * Number of characters to write 155 * 156 * @throws IndexOutOfBoundsException 157 * Implementations should throw this exception 158 * if {@code off} is negative, or {@code len} is negative, 159 * or {@code off + len} is negative or greater than the length 160 * of the given string 161 * 162 * @throws IOException 163 * If an I/O error occurs 164 */ 165 void write(string str, int off, int len) { 166 synchronized (lock) { 167 write(cast(byte[])str[off .. off + len]); 168 } 169 } 170 171 172 /** 173 * Appends the specified character sequence to this writer. 174 * 175 * <p> An invocation of this method of the form {@code out.append(csq)} 176 * behaves in exactly the same way as the invocation 177 * 178 * <pre> 179 * out.write(csq.toString()) </pre> 180 * 181 * <p> Depending on the specification of {@code toString} for the 182 * character sequence {@code csq}, the entire sequence may not be 183 * appended. For instance, invoking the {@code toString} method of a 184 * character buffer will return a subsequence whose content depends upon 185 * the buffer's position and limit. 186 * 187 * @param csq 188 * The character sequence to append. If {@code csq} is 189 * {@code null}, then the four characters {@code "null"} are 190 * appended to this writer. 191 * 192 * @return This writer 193 * 194 * @throws IOException 195 * If an I/O error occurs 196 * 197 */ 198 Writer append(const(char)[] csq) { 199 write(cast(string)csq); 200 return this; 201 } 202 203 /** 204 * Appends a subsequence of the specified character sequence to this writer. 205 * {@code Appendable}. 206 * 207 * <p> An invocation of this method of the form 208 * {@code out.append(csq, start, end)} when {@code csq} 209 * is not {@code null} behaves in exactly the 210 * same way as the invocation 211 * 212 * <pre>{@code 213 * out.write(csq.subSequence(start, end).toString()) 214 * }</pre> 215 * 216 * @param csq 217 * The character sequence from which a subsequence will be 218 * appended. If {@code csq} is {@code null}, then characters 219 * will be appended as if {@code csq} contained the four 220 * characters {@code "null"}. 221 * 222 * @param start 223 * The index of the first character in the subsequence 224 * 225 * @param end 226 * The index of the character following the last character in the 227 * subsequence 228 * 229 * @return This writer 230 * 231 * @throws IndexOutOfBoundsException 232 * If {@code start} or {@code end} are negative, {@code start} 233 * is greater than {@code end}, or {@code end} is greater than 234 * {@code csq.length()} 235 * 236 * @throws IOException 237 * If an I/O error occurs 238 * 239 */ 240 Writer append(const(char)[] csq, int start, int end) { 241 if (csq.empty()) csq = "null"; 242 return append(csq[start .. end]); 243 } 244 245 246 /** 247 * Appends the specified character to this writer. 248 * 249 * <p> An invocation of this method of the form {@code out.append(c)} 250 * behaves in exactly the same way as the invocation 251 * 252 * <pre> 253 * out.write(c) </pre> 254 * 255 * @param c 256 * The 16-bit character to append 257 * 258 * @return This writer 259 * 260 * @throws IOException 261 * If an I/O error occurs 262 */ 263 Writer append(char c) { 264 write(c); 265 return this; 266 } 267 268 /** 269 * Flushes the stream. If the stream has saved any characters from the 270 * various write() methods in a buffer, write them immediately to their 271 * intended destination. Then, if that destination is another character or 272 * byte stream, flush it. Thus one flush() invocation will flush all the 273 * buffers in a chain of Writers and OutputStreams. 274 * 275 * <p> If the intended destination of this stream is an abstraction provided 276 * by the underlying operating system, for example a file, then flushing the 277 * stream guarantees only that bytes previously written to the stream are 278 * passed to the operating system for writing; it does not guarantee that 279 * they are actually written to a physical device such as a disk drive. 280 * 281 * @throws IOException 282 * If an I/O error occurs 283 */ 284 abstract void flush(); 285 286 /** 287 * Closes the stream, flushing it first. Once the stream has been closed, 288 * further write() or flush() invocations will cause an IOException to be 289 * thrown. Closing a previously closed stream has no effect. 290 * 291 * @throws IOException 292 * If an I/O error occurs 293 */ 294 abstract void close(); 295 } 296 297 298 /** 299 * Returns a new {@code Writer} which discards all characters. The 300 * returned stream is initially open. The stream is closed by calling 301 * the {@code close()} method. Subsequent calls to {@code close()} have 302 * no effect. 303 * 304 * <p> While the stream is open, the {@code append(char)}, {@code 305 * append(CharSequence)}, {@code append(CharSequence, int, int)}, 306 * {@code flush()}, {@code write(int)}, {@code write(char[])}, and 307 * {@code write(char[], int, int)} methods do nothing. After the stream 308 * has been closed, these methods all throw {@code IOException}. 309 * 310 * <p> The {@link #lock object} used to synchronize operations on the 311 * returned {@code Writer} is not specified. 312 * 313 * @return a {@code Writer} which discards all characters 314 * 315 */ 316 class NullWriter : Writer { 317 private shared bool closed; 318 319 private void ensureOpen() { 320 if (closed) { 321 throw new IOException("Stream closed"); 322 } 323 } 324 325 326 override Writer append(char c) { 327 ensureOpen(); 328 return this; 329 } 330 331 override void write(int c) { 332 ensureOpen(); 333 } 334 335 override void write(byte[] cbuf, int off, int len) { 336 assert(off+len <= cbuf.length); 337 ensureOpen(); 338 } 339 340 341 override void write(string str) { 342 assert(!str.empty()); 343 ensureOpen(); 344 } 345 346 347 override void write(string str, int off, int len) { 348 assert(off+len <= str.length); 349 ensureOpen(); 350 } 351 352 353 override void flush() { 354 ensureOpen(); 355 } 356 357 358 override void close() { 359 closed = true; 360 } 361 } 362 363 /** 364 Helper for Writer to create a null writer.*/ 365 Writer nullWriter() { 366 return new NullWriter(); 367 }