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.FileOutputStream;
13 
14 import hunt.Exceptions;
15 import hunt.logging;
16 import hunt.stream.Common;
17 
18 import std.array;
19 import std.stdio;
20 
21 
22 /**
23  * A file output stream is an output stream for writing data to a
24  * <code>File</code> or to a <code>FileDescriptor</code>. Whether or not
25  * a file is available or may be created depends upon the underlying
26  * platform.  Some platforms, in particular, allow a file to be opened
27  * for writing by only one {@code FileOutputStream} (or other
28  * file-writing object) at a time.  In such situations the constructors in
29  * this class will fail if the file involved is already open.
30  *
31  * <p><code>FileOutputStream</code> is meant for writing streams of raw bytes
32  * such as image data. For writing streams of characters, consider using
33  * <code>FileWriter</code>.
34  *
35  * @apiNote
36  * To release resources used by this stream {@link #close} should be called
37  * directly or by try-with-resources. Subclasses are responsible for the cleanup
38  * of resources acquired by the subclass.
39  * Subclasses that override {@link #finalize} in order to perform cleanup
40  * should be modified to use alternative cleanup mechanisms such as
41  * {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method.
42  *
43  * @implSpec
44  * If this FileOutputStream has been subclassed and the {@link #close}
45  * method has been overridden, the {@link #close} method will be
46  * called when the FileInputStream is unreachable.
47  * Otherwise, it is implementation specific how the resource cleanup described in
48  * {@link #close} is performed.
49  *
50  * @author  Arthur van Hoff
51  * @see     java.io.File
52  * @see     java.io.FileDescriptor
53  * @see     java.io.FileInputStream
54  * @see     java.nio.file.Files#newOutputStream
55  */
56 class FileOutputStream : OutputStream
57 {
58     /**
59      * Access to FileDescriptor internals.
60      */
61     // private static final JavaIOFileDescriptorAccess fdAccess =
62     //     SharedSecrets.getJavaIOFileDescriptorAccess();
63 
64     /**
65      * The system dependent file descriptor.
66      */
67     // private FileDescriptor fd;
68 
69     /**
70      * The associated channel, initialized lazily.
71      */
72     // private FileChannel channel;
73     private File file;
74 
75     /**
76      * The path of the referenced file
77      * (null if the stream is created with a file descriptor)
78      */
79     // private string path;
80 
81     private Object closeLock;
82 
83     private bool closed;
84 
85     // private Object altFinalizer;
86 
87     /**
88      * Creates a file output stream to write to the file with the
89      * specified name. A new <code>FileDescriptor</code> object is
90      * created to represent this file connection.
91      * <p>
92      * First, if there is a security manager, its <code>checkWrite</code>
93      * method is called with <code>name</code> as its argument.
94      * <p>
95      * If the file exists but is a directory rather than a regular file, does
96      * not exist but cannot be created, or cannot be opened for any other
97      * reason then a <code>FileNotFoundException</code> is thrown.
98      *
99      * @implSpec Invoking this constructor with the parameter {@code name} is
100      * equivalent to invoking {@link #FileOutputStream(string,bool)
101      * new FileOutputStream(name, false)}.
102      *
103      * @param      name   the system-dependent filename
104      * @exception  FileNotFoundException  if the file exists but is a directory
105      *                   rather than a regular file, does not exist but cannot
106      *                   be created, or cannot be opened for any other reason
107      * @exception  SecurityException  if a security manager exists and its
108      *               <code>checkWrite</code> method denies write access
109      *               to the file.
110      * @see        java.lang.SecurityManager#checkWrite(java.lang.string)
111      */
112     this(string name) {
113         this(name, false);
114     }
115 
116     /**
117      * Creates a file output stream to write to the file with the specified
118      * name.  If the second argument is <code>true</code>, then
119      * bytes will be written to the end of the file rather than the beginning.
120      * A new <code>FileDescriptor</code> object is created to represent this
121      * file connection.
122      * <p>
123      * First, if there is a security manager, its <code>checkWrite</code>
124      * method is called with <code>name</code> as its argument.
125      * <p>
126      * If the file exists but is a directory rather than a regular file, does
127      * not exist but cannot be created, or cannot be opened for any other
128      * reason then a <code>FileNotFoundException</code> is thrown.
129      *
130      * @param     name        the system-dependent file name
131      * @param     append      if <code>true</code>, then bytes will be written
132      *                   to the end of the file rather than the beginning
133      * @exception  FileNotFoundException  if the file exists but is a directory
134      *                   rather than a regular file, does not exist but cannot
135      *                   be created, or cannot be opened for any other reason.
136      * @exception  SecurityException  if a security manager exists and its
137      *               <code>checkWrite</code> method denies write access
138      *               to the file.
139      * @see        java.lang.SecurityManager#checkWrite(java.lang.string)
140      */
141     this(string name, bool append) {
142         if(name.empty)
143             throw new NullPointerException();
144         if(append)
145             this(File(name, "a"));
146         else
147             this(File(name, "w"));
148     }
149 
150     /**
151      * Creates a file output stream to write to the file represented by
152      * the specified <code>File</code> object. A new
153      * <code>FileDescriptor</code> object is created to represent this
154      * file connection.
155      * <p>
156      * First, if there is a security manager, its <code>checkWrite</code>
157      * method is called with the path represented by the <code>file</code>
158      * argument as its argument.
159      * <p>
160      * If the file exists but is a directory rather than a regular file, does
161      * not exist but cannot be created, or cannot be opened for any other
162      * reason then a <code>FileNotFoundException</code> is thrown.
163      *
164      * @param      file               the file to be opened for writing.
165      * @exception  FileNotFoundException  if the file exists but is a directory
166      *                   rather than a regular file, does not exist but cannot
167      *                   be created, or cannot be opened for any other reason
168      * @exception  SecurityException  if a security manager exists and its
169      *               <code>checkWrite</code> method denies write access
170      *               to the file.
171      */
172     this(File file) {
173         this.file = file;
174 
175         version(HUNT_DEBUG_MORE) {
176             infof("Outputing to file: %s", file.name());
177         }
178 
179         initialize();
180     }
181 
182     private void initialize() {
183         closeLock = new Object();
184     }
185 
186     /**
187      * Opens a file, with the specified name, for overwriting or appending.
188      * @param name name of file to be opened
189      * @param append whether the file is to be opened in append mode
190      */
191     // private void open0(string name, bool append);
192 
193     // wrap call to allow instrumentation
194     /**
195      * Opens a file, with the specified name, for overwriting or appending.
196      * @param name name of file to be opened
197      * @param append whether the file is to be opened in append mode
198      */
199     // private void open(string name, bool append) {
200     //     open0(name, append);
201     // }
202 
203     /**
204      * Writes the specified byte to this file output stream.
205      *
206      * @param   b   the byte to be written.
207      * @param   append   {@code true} if the write operation first
208      *     advances the position to the end of file
209      */
210     // private void write(int b, bool append);
211 
212     /**
213      * Writes the specified byte to this file output stream. Implements
214      * the <code>write</code> method of <code>OutputStream</code>.
215      *
216      * @param      b   the byte to be written.
217      * @exception  IOException  if an I/O error occurs.
218      */
219     override void write(int b) {
220         // write(b, fdAccess.getAppend(fd));
221         file.rawWrite([cast(byte)b]);
222     }
223 
224     /**
225      * Writes a sub array as a sequence of bytes.
226      * @param b the data to be written
227      * @param off the start offset in the data
228      * @param len the number of bytes that are written
229      * @param append {@code true} to first advance the position to the
230      *     end of file
231      * @exception IOException If an I/O error has occurred.
232      */
233     // private void writeBytes(byte b[], int off, int len, bool append);
234 
235     /**
236      * Writes <code>b.length</code> bytes from the specified byte array
237      * to this file output stream.
238      *
239      * @param      b   the data.
240      * @exception  IOException  if an I/O error occurs.
241      */
242     override void write(byte[] b) {
243         // writeBytes(b, 0, b.length, fdAccess.getAppend(fd));
244         file.rawWrite(b);
245     }
246 
247     /**
248      * Writes <code>len</code> bytes from the specified byte array
249      * starting at offset <code>off</code> to this file output stream.
250      *
251      * @param      b     the data.
252      * @param      off   the start offset in the data.
253      * @param      len   the number of bytes to write.
254      * @exception  IOException  if an I/O error occurs.
255      */
256     override void write(byte[] b, int off, int len) {
257         // writeBytes(b, off, len, fdAccess.getAppend(fd));
258         file.rawWrite(b[off .. off+len]);
259     }
260 
261     /**
262      * Closes this file output stream and releases any system resources
263      * associated with this stream. This file output stream may no longer
264      * be used for writing bytes.
265      *
266      * <p> If this stream has an associated channel then the channel is closed
267      * as well.
268      *
269      * @apiNote
270      * Overriding {@link #close} to perform cleanup actions is reliable
271      * only when called directly or when called by try-with-resources.
272      * Do not depend on finalization to invoke {@code close};
273      * finalization is not reliable and is deprecated.
274      * If cleanup of resources is needed, other mechanisms such as
275      * {@linkplain java.lang.ref.Cleaner} should be used.
276      *
277      * @exception  IOException  if an I/O error occurs.
278      *
279      * @revised 1.4
280      * @spec JSR-51
281      */
282     override void close() {
283         if (closed) 
284             return;
285 
286         synchronized (closeLock) {
287             if (closed) return;
288             closed = true;
289         }
290 
291         this.file.close();
292 
293         // FileChannel fc = channel;
294         // if (fc != null) {
295         //     // possible race with getChannel(), benign since
296         //     // FileChannel.close is final and idempotent
297         //     fc.close();
298         // }
299 
300         // fd.closeAll(new Closeable() {
301         //     void close() {
302         //        fd.close();
303         //    }
304         // });
305     }
306 
307     /**
308      * Returns the file descriptor associated with this stream.
309      *
310      * @return  the <code>FileDescriptor</code> object that represents
311      *          the connection to the file in the file system being used
312      *          by this <code>FileOutputStream</code> object.
313      *
314      * @exception  IOException  if an I/O error occurs.
315      * @see        java.io.FileDescriptor
316      */
317     //  final FileDescriptor getFD()  {
318     //     if (fd != null) {
319     //         return fd;
320     //     }
321     //     throw new IOException();
322     //  }
323 
324     /**
325      * Returns the unique {@link java.nio.channels.FileChannel FileChannel}
326      * object associated with this file output stream.
327      *
328      * <p> The initial {@link java.nio.channels.FileChannel#position()
329      * position} of the returned channel will be equal to the
330      * number of bytes written to the file so far unless this stream is in
331      * append mode, in which case it will be equal to the size of the file.
332      * Writing bytes to this stream will increment the channel's position
333      * accordingly.  Changing the channel's position, either explicitly or by
334      * writing, will change this stream's file position.
335      *
336      * @return  the file channel associated with this file output stream
337      *
338      * @spec JSR-51
339      */
340     // FileChannel getChannel() {
341     //     FileChannel fc = this.channel;
342     //     if (fc == null) {
343     //         synchronized (this) {
344     //             fc = this.channel;
345     //             if (fc == null) {
346     //                 this.channel = fc = FileChannelImpl.open(fd, path, false,
347     //                     true, false, this);
348     //                 if (closed) {
349     //                     try {
350     //                         // possible race with close(), benign since
351     //                         // FileChannel.close is final and idempotent
352     //                         fc.close();
353     //                     } catch (IOException ioe) {
354     //                         throw new InternalError(ioe); // should not happen
355     //                     }
356     //                 }
357     //             }
358     //         }
359     //     }
360     //     return fc;
361     // }
362 
363     /**
364      * Cleans up the connection to the file, and ensures that the
365      * {@link #close} method of this file output stream is
366      * called when there are no more references to this stream.
367      * The {@link #finalize} method does not call {@link #close} directly.
368      *
369      * @apiNote
370      * To release resources used by this stream {@link #close} should be called
371      * directly or by try-with-resources.
372      *
373      * @implSpec
374      * If this FileOutputStream has been subclassed and the {@link #close}
375      * method has been overridden, the {@link #close} method will be
376      * called when the FileOutputStream is unreachable.
377      * Otherwise, it is implementation specific how the resource cleanup described in
378      * {@link #close} is performed.
379      *
380      * @deprecated The {@code finalize} method has been deprecated and will be removed.
381      *     Subclasses that override {@code finalize} in order to perform cleanup
382      *     should be modified to use alternative cleanup mechanisms and
383      *     to remove the overriding {@code finalize} method.
384      *     When overriding the {@code finalize} method, its implementation must explicitly
385      *     ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}.
386      *     See the specification for {@link Object#finalize()} for further
387      *     information about migration options.
388      *
389      * @exception  IOException  if an I/O error occurs.
390      * @see        java.io.FileInputStream#close()
391      */
392     // @Deprecated(since="9", forRemoval = true)
393     // protected void finalize() {
394     // }
395 
396     // private static void initIDs();
397 
398     // static {
399     //     initIDs();
400     // }
401 
402     /*
403      * Returns a finalizer object if the FOS needs a finalizer; otherwise null.
404      * If the FOS has a close method; it needs an AltFinalizer.
405      */
406     // private static Object getFinalizer(FileOutputStream fos) {
407     //     Class<?> clazz = fos.getClass();
408     //     while (clazz != FileOutputStream.class) {
409     //         try {
410     //             clazz.getDeclaredMethod("close");
411     //             return new AltFinalizer(fos);
412     //         } catch (NoSuchMethodException nsme) {
413     //             // ignore
414     //         }
415     //         clazz = clazz.getSuperclass();
416     //     }
417     //     return null;
418     // }
419 
420     /**
421      * Class to call {@code FileOutputStream.close} when finalized.
422      * If finalization of the stream is needed, an instance is created
423      * in its constructor(s).  When the set of instances
424      * related to the stream is unreachable, the AltFinalizer performs
425      * the needed call to the stream's {@code close} method.
426      */
427     // static class AltFinalizer {
428     //     private final FileOutputStream fos;
429 
430     //     this(FileOutputStream fos) {
431     //         this.fos = fos;
432     //     }
433 
434     //     override
435     //     @SuppressWarnings("deprecation")
436     //     protected final void finalize() {
437     //         try {
438     //             if (fos.fd != null) {
439     //                 if (fos.fd == FileDescriptor.out || fos.fd == FileDescriptor.err) {
440     //                     // Subclass may override flush; otherwise it is no-op
441     //                     fos.flush();
442     //                 } else {
443     //                     /* if fd is shared, the references in FileDescriptor
444     //                      * will ensure that finalizer is only called when
445     //                      * safe to do so. All references using the fd have
446     //                      * become unreachable. We can call close()
447     //                      */
448     //                     fos.close();
449     //                 }
450     //             }
451     //         } catch (IOException ioe) {
452     //             // ignore
453     //         }
454     //     }
455     // }
456 
457 }