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