001package Torello.Java;
002
003import java.io.*;
004import java.util.*;
005import java.util.zip.*;
006import java.util.stream.*;
007import java.nio.file.*;
008import java.lang.reflect.InvocationTargetException;
009
010import Torello.Java.Additional.*;
011
012import Torello.JavaDoc.StaticFunctional;
013import Torello.JavaDoc.Excuse;
014
015/**
016 * Operating-System independent File Read & Write utilities that reduce many common Java
017 * File I/O Commands to a single method invocation, focusing heavily on Java's Serialization
018 * feature.
019 * 
020 * <EMBED CLASS='external-html' DATA-FILE-ID=FILE_RW>
021 */
022@StaticFunctional(Excused="TRUNCATE_EOF_CHARS", Excuses=Excuse.FLAG)
023public class FileRW
024{
025    private FileRW() { }
026
027    /**
028     * This is used by method {@link #loadFileToString(String)}.  By default this flag is set to
029     * {@code TRUE}, and when it is, any trailing {@code EOF chars, ASCII-0} found in a file that
030     * is to be interpreted as a Text-File, will be truncated from the {@code String} returned by
031     * that {@code 'reader'} method.
032     */
033    public static boolean TRUNCATE_EOF_CHARS = true;
034
035
036    // ********************************************************************************************
037    // ********************************************************************************************
038    // writeFile
039    // ********************************************************************************************
040    // ********************************************************************************************
041
042
043    /**
044     * Writes the entire contents of a single {@code java.lang.String} to a file on the File-System
045     * named {@code 'fName'}.
046     *
047     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
048     * 
049     * @param s A {@code java.lang.String} which is appended, in entirety, to the file ('fName')
050     *
051     * @param fName The name of the file to which the contents of the {@code java.lang.String}
052     * are appended.  If This file doesn't already exist, it is created here.
053     *
054     * @throws IOException If any I/O errors have occurred with the File-System / disk.
055     */
056    public static void writeFile(CharSequence s, String fName) throws IOException
057    {
058        File outF = new File(fName);
059
060        outF.createNewFile();
061
062        // This writer is 'java.lang.AutoCloseable'.
063        //
064        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
065        //       caught!
066
067        try
068            (FileWriter fw = new FileWriter(outF))
069            { fw.write(s.toString()); }
070    }
071
072    /**
073     * This takes an {@code Iterable<String>}, and a filename, and writes each
074     * {@code java.lang.String} in the {@code Iterator} that it produces to the file 
075     * specified by File-Name parameter {@code 'fName'}
076     *
077     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
078     * 
079     * <BR /><DIV CLASS=JDHintAlt>
080     * <B STYLE='color:red;'>New-Line Characters:</B> A newline {@code ('\n')} is appended to the
081     * end of each {@code java.lang.String} before writing it to the file.
082     * </DIV>
083     *
084     * @param i This is any java {@code Iterable<String>} object.  Each of these will be written,
085     * in succession, to the file named by parameter {@code 'fName'}
086     *
087     * @param fName The name of the file to which the contents of the {@code java.lang.String}
088     * are appended.  If This file doesn't already exist, it is created here.
089     *
090     * @throws IOException If an I/O error has occurred as a result of the File-System or
091     * disk operation.
092     */
093    public static void writeFile(Iterable<String> i, String fName) throws IOException
094    {
095        Iterator<String> iter = i.iterator();
096
097        File outF = new File(fName);
098
099        outF.createNewFile();
100
101        // This writer is 'java.lang.AutoCloseable'.
102        //
103        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
104        //       caught!
105
106        try
107            (FileWriter fw = new FileWriter(outF))
108            { while (iter.hasNext()) fw.write(iter.next() + "\n"); }
109    }
110
111
112    // ********************************************************************************************
113    // ********************************************************************************************
114    // writeFile_NO_NEWLINE
115    // ********************************************************************************************
116    // ********************************************************************************************
117
118
119    /**
120     * This takes an {@code Iterable} of String, and a filename and writes each
121     * {@code java.lang.String} in the {@code Iterator} that it produces to the file specified by
122     * File-Name parameter {@code 'fName'}
123     *
124     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
125     * 
126     * <BR /><DIV CLASS=JDHintAlt>
127     * <B STYLE='color:red;'>New-Line Characters:</B> In this function a newline {@code ('\n')}
128     * character <I>is <B>not</B> appended</I> to the end of each {@code java.lang.String} of the
129     * input {@code Iterator}.
130     * </DIV>
131     *
132     * @param i This is any java {@code 'Iterable'} object that can iterate {@code 'String'}.
133     * Each of these will be written, in succession, to the file named by {@code 'fName'}
134     *
135     * @param fName The name of the file to which the contents of the {@code java.lang.String}
136     * are appended.  If This file doesn't already exist, it is created here.
137     *
138     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
139     * operation.
140     */
141    public static void writeFile_NO_NEWLINE(Iterable<String> i, String fName) throws IOException
142    {
143        Iterator<String> iter = i.iterator();
144
145        File outF = new File(fName);
146
147        outF.createNewFile();
148
149        // This Writer is 'java.lang.AutoCloseable'.
150        //
151        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
152        //       caught!
153
154        try
155            (FileWriter fw = new FileWriter(outF))
156            { while (iter.hasNext()) fw.write(iter.next()); }
157    }
158
159
160    // ********************************************************************************************
161    // ********************************************************************************************
162    // appendToFile
163    // ********************************************************************************************
164    // ********************************************************************************************
165
166
167    /**
168     * Appends the entire input {@code java.lang.String} - actually a
169     * {@code java.lang.CharSequence} to the file on the File-System named {@code 'fName'}
170     * 
171     * <BR /><BR /><DIV CLASS=JDHint>
172     * <B STYLE='color:red;'>Directory Requirements:</B> Though the file does not need to exist
173     * already in order for this file to be written the directory hierarchy needs to exist, or a
174     * Java {@code 'IOException'} will occur.
175     * </DIV>
176     *
177     * @param s A {@code java.lang.CharSequence} (almost identical to {@code 'String'}) which is
178     * appended, in entirety, to the File-Name parameter {@code 'fName'}
179     *
180     * @param fName The name of the file to which the contents of the {@code java.lang.String} are
181     * appended.  If This file doesn't already exist, it is created here.
182     *
183     * @throws IOException If an I/O error has occurred as a result of the File-System or
184     * disk operation.
185     */
186    public static void appendToFile(CharSequence s, String fName) throws IOException
187    {
188        File f = new File(fName);
189
190        if (! f.exists()) f.createNewFile();
191
192        Files.write(Paths.get(fName), s.toString().getBytes(), StandardOpenOption.APPEND);
193    }
194
195    /**
196     * This takes an {@code Iterable<String>}, and a filename, and appends each
197     * {@code java.lang.String} in the {@code Iterator} that it produces to the file 
198     * specified by File-Name parameter {@code 'fName'}
199     *
200     * <BR /><BR /><DIV CLASS=JDHint>
201     * <B STYLE='color:red;'>Directory Requirements:</B> Though the file does not need to exist
202     * already in order for this file to be written the directory hierarchy needs to exist, or a
203     * Java {@code 'IOException'} will occur.
204     * </DIV>
205     *
206     * @param i This is any java {@code Iterable<String>} object.  Each of these will be written,
207     * in succession, to the file named by parameter {@code 'fName'}
208     *
209     * @param fName The name of the file to which the contents of the {@code java.lang.String}
210     * are appended.  If This file doesn't already exist, it is created here.
211     *
212     * @param addNewLines When this parameter is passed {@code TRUE}, a New-Line character will be
213     * appended after each {@code String} that is written.  When {@code FALSE}, only the
214     * {@code String's} produced by the {@code Iterable}, themselves, are appended to the file.
215     * 
216     * @throws IOException If an I/O error has occurred as a result of the File-System or
217     * disk operation.
218     */
219    public static void appendToFile(Iterable<String> i, String fName, boolean addNewLines)
220        throws IOException
221    {
222        File f = new File(fName);
223
224        if (! f.exists()) f.createNewFile();
225
226
227        // Uses the "Ternary Operator" / "Conditional Operator" Syntax, but it's a little hard to
228        // read that.  There is a '?' and a ':' after the boolean 'addNewsLines'
229
230        Iterable<String> passedIterable = addNewLines
231
232
233            // If the user has requested to add newlines, wrap the passed Iterable inside of a new
234            // Iterable that appends a new-line character to the output of the User's 'next()'
235            // method (which is just returning the String's to be written to disk - less the newline)
236
237            ?   new Iterable<>()
238                {
239                    private final Iterator<String> iterInternal = i.iterator();
240
241
242                    // Funny Note: All an "Iterable" is is an Object that returns an Iterator.  The
243                    // interface "Iterable" only has one non-Default Method - iterator().  Re-Write
244                    // that method to return a slightly altered Iterator<String>
245
246                    public Iterator<String> iterator()
247                    {
248                        return new Iterator<String>()
249                        {
250                            public boolean  hasNext()   { return iterInternal.hasNext();        }
251                            public String   next()      { return iterInternal.next() + '\n';    }
252                        };
253                    }
254                }
255
256            // Otherwise, just assign the Users Iterable to the "passedIterable" Variable
257            : i;
258
259        // Now write the Passed Iterable of String, using java.nio.file.Files
260        Files.write(Paths.get(fName), passedIterable, StandardOpenOption.APPEND);
261    }
262
263
264    // ********************************************************************************************
265    // ********************************************************************************************
266    // Load File (as String's}
267    // ********************************************************************************************
268    // ********************************************************************************************
269
270
271    /**
272     * This will load the entire contents of a Text-File on disk to a single
273     * {@code java.lang.String}
274     * 
275     * <BR /><BR /><DIV CLASS=JDHint>
276     * <B STYLE='color:red;'>Trailing Zeros:</B> Some of the ugliest code to see is that which
277     * scans for {@code 'EOF'} characters that have been liberally inserted into a simple
278     * Text-File.  When reading a file (which, regardless of whether it <B>actually is a
279     * Text-File</B>), this method will remove any <B>trailing {@code ASCII-0}</B> characters
280     * (literally, {@code char c == 0}) from the files that are read.
281     * </DIV>
282     * 
283     * <BR />Finding {@code '.html'} or {@code '.java'} files in which some editor (for whatever
284     * reason) has inserted {@code EOF-like} characters to the end of the text can make programming
285     * a headache.
286     * 
287     * <BR /><BR />Suffice it to say, the {@code String} that is returned from this method will
288     * contain the last non-zero character (including CRLF, {@code '\n'} or {@code '\r'}
289     * character that was read from the file.  Operating-systems do not require that a file have a
290     * trailing zero to interpret them.
291     * 
292     * <BR /><BR />Character {@code '0'} is a legacy / older-version of the EOF Marker.
293     * {@code '.java'}-Files certainly don't need them, and they can actually be a problem when a
294     * developer is checking for file's that have equal-{@code String's} in them.
295     *
296     * <BR /><BR /><DIV CLASS=JDHintAlt>
297     * <B STYLE='color:red;'>Static Boolean-Flag:</B> This class (class {@code FileRW}) has a
298     * {@code static, boolean} flag that is able to prevent / shunt this 'Trailing Zero Removing'
299     * behavior.  When {@link #TRUNCATE_EOF_CHARS} is set to {@code FALSE}, reading a file into a
300     * {@code String} using this method will return the {@code String} - <B>including as many 
301     * Trailing Zero-Characters as have been appended to the end of that file</B>.
302     * </DIV>
303     * 
304     * @param fName the File-Name of a valid Text-File in the File-System
305     *
306     * @return The entire contents of the file as a {@code String}.
307     *
308     * @throws IOException If an I/O error has occurred as a result of the File-System or
309     * disk operation.
310     */
311    public static String loadFileToString(String fName) throws IOException
312    {
313        // The reader is  'java.lang.AutoCloseable'.
314        //
315        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
316        //       caught!
317
318        try
319            (FileReader fr = new FileReader(fName))
320        {
321            int     len             = (int) new File(fName).length();
322            char[]  cArr            = new char[len];
323            int     offset          = 0;
324            int     charsRead       = 0;
325            int     charsRemaining  = len;
326
327            while ((offset < len) && ((charsRead = fr.read(cArr, offset, charsRemaining)) != -1))
328            {
329                offset          += charsRead;
330                charsRemaining  -= charsRead;
331            }
332
333            len = cArr.length;
334
335            if (TRUNCATE_EOF_CHARS) while ((len > 0) && (cArr[len-1] == 0)) len--;
336
337            return (len != cArr.length) ? new String(cArr, 0, len) : new String(cArr); 
338        }
339    }
340
341    /**
342     * Convenience Method.
343     * <BR />Invokes: {@link #loadFileToStream(String, boolean)}
344     * <BR />Converts: {@code Stream<String>} into {@code String[]}
345     */
346    public static String[] loadFileToStringArray(String fName, boolean includeNewLine)
347        throws IOException
348    { return loadFileToStream(fName, includeNewLine).toArray(String[]::new); }
349
350    /**
351     * This will load a file to a Java {@code Stream} instance.
352     *
353     * @param fName A File-Name of a valid Text-File in the File-System.
354     * 
355     * @param includeNewLine if this is {@code TRUE}, a {@code '\n'} (newline/CRLF) is appended to
356     * the end of each {@code java.lang.String} read from this file.  If not, the original newline
357     * characters which occur in the file, will be eliminated.
358     * 
359     * <BR /><BR /><DIV CLASS=JDHint>
360     * This method will make one (potentially minor) mistake.  If the final character in the
361     * input-file is, itself, a new-line (if the file ends with a {@code 'CRLF' / 'CR'}), then this
362     * method should return a {@code Stream<String>} that is identical to the original file.
363     * However, <B>if the final character in the file <SPAN STYLE='color:red;'>is not</SPAN> a
364     * new-line {@code '\n'}</B>, then the {@code Stream<String>} that is returned will have an
365     * extra new-line appended to the last {@code String} in the {@code Stream}, and the resultant
366     * {@code Stream<String>} will be longer than the original file by 1 character.
367     * </DIV>
368     *
369     * @return The entire contents of the file as a series of {@code java.lang.String} contained by
370     * a {@code java.util.stream.Stream<String>}.  Converting Java {@code Stream's} to other data
371     * container types is as follows:
372     *
373     * <EMBED CLASS='external-html' DATA-FILE-ID=STRMCNVT>
374     *
375     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
376     * operation.
377     */
378    public static Stream<String> loadFileToStream(String fName, boolean includeNewLine)
379        throws IOException
380    {
381        // These readers's are 'java.lang.AutoCloseable'.
382        //
383        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
384        //       caught!
385        //
386        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
387        //       classes are all (automatically) closed/flushed, in reverse order.
388
389        try (
390            FileReader      fr  = new FileReader(fName);
391            BufferedReader  br  = new BufferedReader(fr);
392        )
393        {
394            Stream.Builder<String>  b = Stream.builder();
395            String                  s = "";
396
397            if (includeNewLine)
398                while ((s = br.readLine()) != null) b.add(s + "\n");
399
400            else
401                while ((s = br.readLine()) != null) b.add(s);
402
403            return b.build();
404        }
405    }
406
407    /**
408     * Convenience Method.
409     * <BR />Invokes: {@link #loadFileToCollection(Collection, String, boolean)}
410     * <BR />Passes: Newly instantiated {@code Vector<String>} to {@code 'collectionChoice'}
411     */
412    public static Vector<String> loadFileToVector(String fName, boolean includeNewLine)
413        throws IOException
414    { return loadFileToCollection(new Vector<String>(), fName, includeNewLine); }
415
416    /**
417     * This method loads the contents of a file to a {@code java.util.Collection<String>} object, 
418     * where each {@code java.lang.String} in the output / returned {@code Collection} is a
419     * different "line of text" from the input-file.  This is identical to invoking:
420     * 
421     * <DIV CLASS=LOC>{@code
422     * Collection<String> myTextFile = FileRW.loadFileToString("someFile.txt").split('\n')
423     * }</DIV>
424     *
425     * <BR /><DIV CLASS=JDHint>
426     * <B STYLE='color:red;'>Variable-Type Parameter:</B> This method uses Java's Variable-Type
427     * Parameter syntax to allow the programmer to decide what type of {@code Collection<String>}
428     * they would like returned from this method.  Common examples would include
429     * {@code Vector<String>, ArrayList<String>, HashSet<String>}, etc...
430     * </DIV>
431     * 
432     * <BR /><B CLASS=JDDescLabel>Loading EOF:</B>
433     * 
434     * <BR />This method will make one small mistake.  If the final character inside the input-file
435     * is, itself, a new-line, then this method will return a Java {@code String-Collection} which
436     * is is identical to the original file.
437     * 
438     * <BR /><BR />However, <I>if the final character in the file <B>is not</B> a new-line
439     * {@code '\n'} character</I>, then the returned {@code Collection} will have an extra new-line
440     * appended immediately after the last {@code String} in the {@code Collection}.  Furthermore,
441     * the resultant {@code Collection<String>} will be longer than the original file by 1 character.
442     *
443     * @param collectionChoice This must be an instance of a class that extends Java's
444     * base {@code class Collection<String>} - <I>using {@code 'String'} as the generic-type
445     * parameter.</I>  It will be populated with the lines from a Text-File using the
446     * {@code Collection.add(String)} method.
447     * 
448     * @param <T> This may be any class which extends {@code java.util.Collection}.  It is
449     * specified as a "Type Parameter" because this collection is returned as a result of this
450     * function.  Perhaps it is superfluous to return the same reference that is provided by the
451     * user as input to this method, but this certainly doesn't change the method signature or
452     * make it more complicated.
453     *
454     * @param fName the File-Name of a valid Text-File on the File-System.
455     *
456     * @param includeNewLine if this is {@code TRUE}, a {@code '\n'} (newline/CRLF) is appended to
457     * the end of each {@code java.lang.String} read from this file.  If not, the original newline
458     * characters which occur in the file, will be eliminated.
459     *
460     * @return An identical reference to the reference passed to parameter
461     * {@code 'collectionChoice'}
462     *
463     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
464     * operation.
465     */
466    public static <T extends Collection<String>> T loadFileToCollection
467        (T collectionChoice, String fName, boolean includeNewLine)
468        throws IOException
469    {
470        // These readers's are 'java.lang.AutoCloseable'.
471        //
472        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
473        //       caught!
474        //
475        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
476        //       classes are all (automatically) closed/flushed, in reverse order.
477
478        try (
479            FileReader      fr  = new FileReader(fName);
480            BufferedReader  br  = new BufferedReader(fr);
481        )
482        {
483            String s = "";
484
485            if (includeNewLine)
486                while ((s = br.readLine()) != null) collectionChoice.add(s + "\n");
487
488            else
489                while ((s = br.readLine()) != null) collectionChoice.add(s);
490        }
491
492        return collectionChoice;
493    }
494
495
496    // ********************************************************************************************
497    // ********************************************************************************************
498    // Write ONE Object To File
499    // ********************************************************************************************
500    // ********************************************************************************************
501
502
503    /**
504     * Convenience Method.
505     * <BR />Invokes: {@link #writeObjectToFile(Object, String, boolean)}
506     * <BR />Catches Exception
507     */
508    public static boolean writeObjectToFileNOCNFE(Object o, String fName, boolean ZIP)
509        throws IOException
510    {
511        try 
512            { writeObjectToFile(o, fName, ZIP); return true; }
513
514        catch (ClassNotFoundException cnfe) { return false; }
515    }
516
517    /**
518     * Writes a {@code java.lang.Object} to a file for storage, and future reference.
519     * 
520     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
521     * 
522     * @param o An {@code Object} to be written to a file as a <I><B>"Serializable"</B></I>
523     * {@code java.lang.Object}
524     *
525     * @param fName The name of the output file
526     *
527     * @param ZIP a boolean that, when {@code TRUE}, will cause the object's data to be compressed
528     * before being written to the output file.
529     *
530     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
531     * operation.
532     *
533     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
534     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
535     * the classpath.
536     */
537    public static void writeObjectToFile(Object o, String fName, boolean ZIP) 
538        throws IOException, ClassNotFoundException
539    {
540        // These stream's are 'java.lang.AutoCloseable'.
541        //
542        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
543        //       method if they occur.  They are not caught!
544        //
545        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
546        //       classes are all (automatically) closed/flushed, in reverse order.
547
548        if (ZIP)
549
550            try (
551                FileOutputStream    fos     = new FileOutputStream(fName);
552                GZIPOutputStream    gzip    = new GZIPOutputStream(fos);
553                ObjectOutputStream  oos     = new ObjectOutputStream(gzip);
554            )
555                { oos.writeObject(o); }
556
557        else
558
559            try (
560                FileOutputStream    fos     = new FileOutputStream(fName);
561                ObjectOutputStream  oos     = new ObjectOutputStream(fos);
562            )
563                { oos.writeObject(o); }
564    }
565
566
567    // ********************************************************************************************
568    // ********************************************************************************************
569    // Write ALL Objects ToFile
570    // ********************************************************************************************
571    // ********************************************************************************************
572
573
574    /**
575     * Convenience Method.
576     * <BR />Invokes: {@link #writeAllObjectsToFile(Iterable, String, boolean)}
577     * <BR />Catches Exception
578     */
579    public static boolean writeAllObjectsToFileNOCNFE(Iterable<?> i, String fName, boolean ZIP)
580        throws IOException
581    {
582        try
583            { writeAllObjectsToFile(i, fName, ZIP); return true; }
584
585        catch (ClassNotFoundException cnfe) { return false; }
586    }
587
588    /**
589     * Writes a series of {@code java.lang.Object} to a file for storage, and future reference.
590     * 
591     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
592     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
593     * 
594     * @param i A series, {@code Collection}, or {@code List} of {@code Object's} to be written
595     * to a file in <I><B>Serializable</B></I> format.
596     *
597     * @param fName The name of the output file
598     *
599     * @param ZIP a {@code boolean} that, when {@code TRUE}, will cause the {@code Object's} 
600     * data to be compressed before being written to the output-file.
601     *
602     * @throws IOException If an I/O error has occurred as a result of the File-System or
603     * disk operation.
604     *
605     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
606     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
607     * the {@code CLASSPATH}.
608     */
609    public static void writeAllObjectsToFile(Iterable<?> i, String fName, boolean ZIP)
610        throws IOException, ClassNotFoundException
611    {
612        Iterator<?> iter = i.iterator();
613
614        // These stream's are 'java.lang.AutoCloseable'.
615        //
616        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
617        //       method if they occur.  They are not caught!
618        //
619        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
620        //       classes are all (automatically) closed/flushed, in reverse order.
621
622        if (ZIP)
623
624            try (
625                FileOutputStream    fos     = new FileOutputStream(fName);
626                GZIPOutputStream    gzip    = new GZIPOutputStream(fos);
627                ObjectOutputStream  oos     = new ObjectOutputStream(gzip);
628            )
629                { while (iter.hasNext()) oos.writeObject(iter.next()); }
630
631        else
632
633            try (
634                FileOutputStream    fos     = new FileOutputStream(fName);
635                ObjectOutputStream  oos     = new ObjectOutputStream(fos);
636            )
637                { while (iter.hasNext()) oos.writeObject(iter.next()); }
638    }
639
640
641    // ********************************************************************************************
642    // ********************************************************************************************
643    // read ONE Object From File
644    // ********************************************************************************************
645    // ********************************************************************************************
646
647
648    /**
649     * Convenience Method.
650     * <BR />Invokes: {@link #readObjectFromFile(String, boolean)}
651     * <BR />Catches Exception
652     */
653    public static Object readObjectFromFileNOCNFE(String fName, boolean ZIP) throws IOException
654    {
655        try 
656            { return readObjectFromFile(fName, ZIP); }
657
658        catch (ClassNotFoundException cnfe) { return null; }
659    }
660
661    /**
662     * Reads an {@code Object} from a Data-File which must contain a serialized
663     * {@code java.lang.Object}.
664     *
665     * <DIV CLASS="EXAMPLE">{@code
666     * // Create some Object for writing to the File-System, using Object Serialization
667     * int[] dataArr = some_data_method();
668     *
669     * // It is always easier to pass 'true' to the compression boolean parameter
670     * FileRW.writeObjectToFile(dataArr, "data/myDataFile.dat", true);
671     *
672     * ...
673     *
674     * // Later on, this file may be read back into the program, using this call:
675     * Object o = FileRW.readObjectFromFile("data/myDataFile.dat", true);
676     *
677     * // This check prevents compiler-time warnings.  The Annotation "SuppressWarnings" 
678     * // would also work.
679     * dataArr = (o instanceof int[]) ? (int[]) o : null;
680     * }</DIV>
681     *
682     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
683     *
684     * @param fName The name of a Data-File that contains a serialized {@code java.lang.Object}
685     *
686     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains a
687     * Zip-Compressed {@code Object}
688     *
689     * @return The {@code Object} that was written to the Data-File.
690     *
691     * @throws IOException If an I/O error has occurred as a result of the File-System or
692     * disk operation.
693     *
694     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
695     * Machine (JVM) tries to load a particular class and the specified class cannot be found
696     * in the {@code CLASSPATH}.
697     */
698    public static Object readObjectFromFile(String fName, boolean ZIP)
699        throws IOException, ClassNotFoundException
700    {
701        // These stream's are 'java.lang.AutoCloseable'.
702        //
703        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
704        //       method if they occur.  They are not caught!
705        //
706        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
707        //       classes are all (automatically) closed/flushed, in reverse order.
708
709        try (
710            FileInputStream     fis = new FileInputStream(fName);
711            ObjectInputStream   ois = ZIP
712                                    ? new ObjectInputStream(new GZIPInputStream(fis))
713                                    : new ObjectInputStream(fis);
714        )
715            { return ois.readObject(); }
716    }
717
718    /**
719     * Convenience Method.
720     * <BR />Invokes: {@link #readObjectFromFile(String, Class, boolean)}
721     * <BR />Catches Exception
722     */
723    public static <T> T readObjectFromFileNOCNFE(String fName, Class<T> c, boolean ZIP)
724        throws IOException
725    {
726        try
727            { return readObjectFromFile(fName, c, ZIP); }
728
729        catch (ClassNotFoundException cnfe) { return null; }
730    }
731
732    /**
733     * Reads an {@code Object} from a Data-File which must contain a serialized
734     * {@code java.lang.Object}.
735     *
736     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
737     *
738     * @param fName The name of a Data-File that contains a serialized {@code java.lang.Object}
739     *
740     * @param c This is the type of the {@code Object} expecting to be read from disk.  A value
741     * for this parameter can always be obtained by referencing the {@code static} field
742     * {@code '.class'} which is attached to <I>every object</I> in java.
743     *
744     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_CLASS_T>
745     * 
746     * @param <T> This should be set to the return type of this method.  By passing the expecred
747     * return-type class, you may conveniently avoid having to cast the returned instance, or worry
748     * about suppressing compiler warnings.
749     * 
750     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains a
751     * Zip-Compressed {@code Object}
752     *
753     * @return The {@code Object} that was read from the Data-File.
754     *
755     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
756     * operation.
757     *
758     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
759     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
760     * the {@code CLASSPATH}.
761     */
762    public static <T> T readObjectFromFile(String fName, Class<T> c, boolean ZIP)
763        throws IOException, ClassNotFoundException
764    {
765        Object o = readObjectFromFile(fName, ZIP);
766
767        if (o == null)          return null;
768        if (c.isInstance(o))    return c.cast(o);
769
770        throw new ClassNotFoundException(
771            "Although an object was indeed read from the file you have named [" + fName + "], " +
772            "that object was not an instance of [" + c.getName() + "], " + 
773            "but rather of [" + o.getClass().getName() + "]"
774        );
775    }
776
777
778    // ********************************************************************************************
779    // ********************************************************************************************
780    // read ALL OBJECTS FromFile
781    // ********************************************************************************************
782    // ********************************************************************************************
783
784
785    /**
786     * Convenience Method.
787     * <BR />Invokes: {@link #readAllObjects(Class, Collection, String, boolean)}
788     * <BR />Passes: {@code Object.class} to {@code 'objType'}
789     * <BR />Passes: Newly instantiated {@code Vector<Object>} to {@code 'collection'}
790     */
791    public static Vector<Object> readAllObjectsToVector(String fName, boolean ZIP)
792        throws IOException, ClassNotFoundException
793    { return readAllObjects(Object.class, new Vector<Object>(), fName, ZIP); }
794
795    /**
796     * Convenience Method.
797     * <BR />Invokes: {@link #readAllObjects(Class, Collection, String, boolean)}
798     * <BR />Passes: Newly instantiated {@code Vector<T>} to {@code 'collection'}
799     */
800    public static <T> Vector<T> readAllObjectsToVector(Class<T> objType, String fName, boolean ZIP)
801        throws IOException, ClassNotFoundException
802    { return readAllObjects(objType, new Vector<T>(), fName, ZIP); }
803
804    /**
805     * Reads all {@code Object's} found inside a Data-File.  This Data-File should contain only
806     * java-serialized {@code java.lang.Object's}.
807     *
808     * @param objType <EMBED CLASS='external-html' DATA-FILE-ID=FRW_OBJTYPE_PARAM>
809     * 
810     * @param <T> This allows the programmer to inform this method what type of {@code Object's}
811     * are stored in the Serialized Object File, specified by {@code 'fName'}.
812     *
813     * @param collection This should be the desired {@code Collection<E>} that the programmer
814     * would like be populated with the instances of type {@code 'objType'} read from file
815     * {@code 'fName'}.  Variable-Type parameter {@code 'E'} needs to be equal-to or an 
816     * ancestor of {@code 'objType'}
817     * 
818     * @param <U> Merely for convenience, this method returns the {@code Collection} instance that
819     * is passed (by parameter {@code 'Collection'}) as a result of this function.  Therefore, the
820     * Type Parameter {@code 'U'} identifies the Return Type of this method.
821     *
822     * @param fName The name of a Data-File that contains serialized {@code Object's}
823     *
824     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains Zip-Compressed
825     * objects
826     *
827     * @return A reference to the {@code Collection} that was passed to this method.
828     *
829     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
830     * operation.
831     */
832    public static <T, U extends Collection<T>> U readAllObjects
833        (Class<T> objType, U collection, String fName, boolean ZIP)
834        throws IOException, ClassNotFoundException
835    {
836        // Temporary Variable, used in both versions of this method
837        Object o;
838
839        // These stream's are 'java.lang.AutoCloseable'.
840        //
841        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
842        //       method if they occur.  They are not caught!
843        //
844        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
845        //       classes are all (automatically) closed/flushed, in reverse order.
846
847        if (ZIP)
848
849            try (
850                FileInputStream     fis = new FileInputStream(fName);
851                GZIPInputStream     gis = new GZIPInputStream(fis);
852                ObjectInputStream   ois = new ObjectInputStream(gis);
853            )
854            {  
855                while ((o = ois.readObject()) != null)
856
857                    if (objType.isInstance(o))
858                        collection.add(objType.cast(o));
859
860                    else throw new ClassNotFoundException(
861                        "At least one of the objects in the serialized object file " +
862                            "[" + fName + "], " +
863                        "was not an instance of [" + objType.getName() + "], " +
864                        "but rather [" + o.getClass().getName() + "]"
865                    );
866            }
867
868            catch (EOFException eofe) { }
869
870        else
871
872            try (
873                FileInputStream     fis = new FileInputStream(fName);
874                ObjectInputStream   ois = new ObjectInputStream(fis);
875            )
876            {  
877                while ((o = ois.readObject()) != null)
878
879                    if (objType.isInstance(o))
880                        collection.add(objType.cast(o));
881
882                    else throw new ClassNotFoundException(
883                        "At least one of the objects in the serialized object file " +
884                            "[" + fName + "], " +
885                        "was not an instance of [" + objType.getName() + "], " +
886                        "but rather [" + o.getClass().getName() + "]"
887                    );
888            }
889
890            catch (EOFException eofe) { }
891
892        return collection;
893    }
894
895    /**
896     * Convenience Method.
897     * <BR />Invokes: {@link #readAllObjectsToStream(Class, String, boolean)}
898     * <BR />Passes: {@code Object.class} to {@code 'objType'}
899     */
900    public static Stream<Object> readAllObjectsToStream(String fName, boolean ZIP)
901        throws IOException, ClassNotFoundException
902    { return readAllObjectsToStream(Object.class, fName, ZIP); }
903
904    /**
905     * Reads all objects found inside a Data-File.  This Data-File should contain only
906     * java-serialized {@code Object's}.
907     *
908     * @param fName The name of a Data-File that contains the serialized {@code Object's}
909     *
910     * @param objType This is the type of the {@code Object's} expecting to be read from disk.  A
911     * value for this parameter can always be obtained by referencing the static field
912     * {@code '.class'} which is attached to <I>every {@code Object}</I> in Java.  For instance, to
913     * read a Data-File containing a series of {@code Date} instances, use {@code Date.class} as the
914     * value to pass to this parameter.
915     * 
916     * @param <T> This parameter informs this method what type of Serialized Objects are saved
917     * within {@code 'fName'}.  The Java {@code Stream} that is returned as a result of this method
918     * will have the type {@code Stream<T>}.
919     *
920     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains Zip-Compressed
921     * {@code Object's}
922     *
923     * @return A {@code Stream<T>} of all {@code Object's} found in the Data-File.  Converting
924     * Java {@code Stream's} to other Data-Container types is as follows:
925     *
926     * <EMBED CLASS='external-html' DATA-FILE-ID=STREAM_CONVERT_T>
927     *
928     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
929     * operation.
930     *
931     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
932     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
933     * the classpath.
934     */
935    public static <T> Stream<T> readAllObjectsToStream(Class<T> objType, String fName, boolean ZIP)
936        throws IOException, ClassNotFoundException
937    {
938        Stream.Builder<T>   b   = Stream.builder();
939        Object              o   = null;
940
941        try(
942            FileInputStream fis = new FileInputStream(fName);
943
944            ObjectInputStream ois = ZIP
945                ? new ObjectInputStream(new GZIPInputStream(fis))
946                : new ObjectInputStream(fis);
947        )
948        {
949            while ((o = ois.readObject()) != null)
950
951                if (objType.isInstance(o)) b.accept(objType.cast(o));
952
953                else throw new ClassNotFoundException(
954                    "At least one of the objects in the serialized object file [" + fName + "], " +
955                    "was not an instance of [" + objType.getName() + "], " +
956                    "but rather [" + o.getClass().getName() + "]"
957                );
958        }
959
960        catch (EOFException eofe) { }
961
962        return b.build();
963    }
964
965
966    // ********************************************************************************************
967    // ********************************************************************************************
968    // Base-64 Read / Write Stuff (Text File)
969    // ********************************************************************************************
970    // ********************************************************************************************
971
972
973    /**
974     * Uses Java's {@code Object} serialization mechanism to serialize a {@code java.lang.Object},
975     * and then uses the {@code Base64 String-MIME Encoding} system, also provided by java, to
976     * convert the {@code Object} into a text-safe {@code java.lang.String} that may be viewed,
977     * e-mailed, written to a web-page, etc.
978     *
979     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
980     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_EXAMPLE_01>
981     *
982     * @param o This may be any serializable {@code java.lang.Object} instance.  It will be written
983     * to a Text-File after first serializing the {@code Object}, and then next converting the
984     * serialized data-bits to MIME-safe encoded text.
985     *
986     * @param fileName The fileName that will be used to save this {@code Object} as a Text-File.
987     */
988    public static void writeObjectToTextFile(Object o, String fileName)
989        throws IOException
990    { FileRW.writeFile(StringParse.objToB64MimeStr(o), fileName); }
991
992    /**
993     * This will read a java serialized, and MIME-Converted, MIME-Safe {@code java.lang.String}
994     * from a text file and return the {@code Object} that it represented
995     *
996     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
997     *
998     * @param fileName The name of the file containing the MIME-Encoded Serialized
999     * {@code java.lang.Object}.
1000     *
1001     * @return The {@code Object} that had been encoded into the Text-File.
1002     */
1003    public static Object readObjectFromTextFile(String fileName) throws IOException
1004    { return StringParse.b64MimeStrToObj(loadFileToString(fileName)); }
1005
1006    /**
1007     * Convenience Method.
1008     * <BR />Invokes: {@link #readObjectFromTextFile(String, Class)}
1009     * <BR />Catches Exception
1010     */
1011    public static <T> T readObjectFromTextFileNOCNFE(String fileName, Class<T> c)
1012        throws IOException
1013    {
1014        try
1015            { return readObjectFromTextFile(fileName, c); }
1016
1017        catch (ClassNotFoundException cnfe) { return null; }
1018    }
1019
1020    /**
1021     * This will read a java serialized, and MIME-Converted, MIME-Safe {@code java.lang.String}
1022     * from a text file and return the {@code Object} that it represented
1023     *
1024     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
1025     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_EXAMPLE_01>
1026     *
1027     * @param fileName The name of the file containing the MIME-Encoded Serialized
1028     * {@code java.lang.Object}.
1029     *
1030     * @param c This is the type of the {@code Object} expecting to be read from disk.  A value
1031     * for this parameter can always be obtained by referencing the {@code static} field
1032     * {@code '.class'}, which is attached to <I>every {@code Object}</I> in java.
1033     *
1034     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_CLASS_T>
1035     * 
1036     * @param <T> This Type Parameter informs this method what type of Base-64 Serialized Object
1037     * is saved within Text File {@code 'fileName'}.  The returned result of this method will have
1038     * this type, for convenience to avoid casting the {@code Object} (<I>unless that
1039     * {@code Object} is a Java Generic, and you wish to avoid a "Raw Types" warning</I>.  See 
1040     * further details inside the explanation about parameter {@code 'c'}).
1041     *
1042     * @return The {@code Object} that had been encoded into the Text-File.
1043     *
1044     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
1045     * operation.
1046     *
1047     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
1048     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
1049     * the {@code CLASSPATH}.
1050     */
1051    public static <T> T readObjectFromTextFile(String fileName, Class<T> c)
1052        throws IOException, ClassNotFoundException
1053    { 
1054        Object o = StringParse.b64MimeStrToObj(loadFileToString(fileName));
1055
1056        if (o == null)          return null;
1057        if (c.isInstance(o))    return c.cast(o);
1058
1059        throw new ClassNotFoundException(
1060            "Although an object was indeed read from the file you have named [" + fileName + "], " +
1061            "that object was not an instance of [" + c.getName() + "], " + 
1062            "but rather of [" + o.getClass().getName() + "]"
1063        );
1064    }
1065
1066
1067    // ********************************************************************************************
1068    // ********************************************************************************************
1069    // Object Input & Object Output Streams
1070    // ********************************************************************************************
1071    // ********************************************************************************************
1072
1073
1074    /**
1075     * Creates a simple {@code ObjectInputStream} - usually if multiple {@code Object's} have been
1076     * written to a single file.  It was better practice to put {@code Object's} in a
1077     * {@code java.util.Vector}, and write one {@code java.util.Vector} during serialization.
1078     * 
1079     * <BR /><BR />This, eventually, can became inadequate when downloading large numbers of HTML
1080     * results, where the need to write a large Data-File (intermittently - by saving intermediate
1081     * results) is needed.
1082     *
1083     * @param fName This is the File-Name of the Data-File where the serialized {@code Object's}
1084     * have been stored.
1085     *
1086     * @param ZIP If this is set to {@code TRUE}, the data will be de-compressed.
1087     *
1088     * @return A java {@code ObjectInputStream}
1089     *
1090     * @throws IOException If an I/O error has occurred as a result of the File-System
1091     * or disk operation.
1092     */
1093    public static ObjectInputStream getOIS(String fName, boolean ZIP) throws IOException
1094    {
1095        return ZIP
1096            ? new ObjectInputStream(new GZIPInputStream(new FileInputStream(new File(fName))))
1097            : new ObjectInputStream(new FileInputStream(new File(fName))); 
1098    }
1099
1100    /**
1101     * Creates a simple {@code ObjectOutputStream} - usually if multiple {@code Object's} need
1102     * to be written to a single file.  It was better practice to put {@code Object's} in a
1103     * {@code java.util.Vector}, and write one {@code java.util.Vector} during serialization.
1104     *
1105     * @param fName This is the File-Name of the Data-File where the serialized {@code Object's}
1106     * will be stored.
1107     *
1108     * @param ZIP If this is set to {@code TRUE}, the data will be compressed.
1109     *
1110     * @return A java {@code ObjectInputStream}
1111     *
1112     * @throws IOException If an I/O error has occurred as a result of the File-System or
1113     * disk operation.
1114     */
1115    public static ObjectOutputStream getOOS(String fName, boolean ZIP) throws IOException
1116    {
1117        return ZIP
1118            ? new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(new File(fName))))
1119            : new ObjectOutputStream(new FileOutputStream(new File(fName))); 
1120    }
1121
1122
1123    // ********************************************************************************************
1124    // ********************************************************************************************
1125    // Copy, Move, Delete
1126    // ********************************************************************************************
1127    // ********************************************************************************************
1128
1129
1130    /**
1131     * This method will perform a byte-for-byte copy of a file from one location to another.
1132     *
1133     * @param inFileName The name of the input-file.  It will be byte-for-byte copied to an output
1134     * File-Name.
1135     *
1136     * @param outFileOrDirName The name of the output-file, or the name of the directory where the
1137     * file shall be moved.
1138     * 
1139     * <BR /><BR />If this file already exists <I>and it is a file (not a directory)</I>
1140     * it will be over-written.
1141     * 
1142     * <BR /><BR />If parameter {@code 'outFileOrDirName'} names a directory - <I>due
1143     * to having an ending {@code File.separator}</I>, <B>but</B> does not appear to name a
1144     * directory that exists, this method will throw a {@code FileNotFoundException}.
1145     * 
1146     * <BR /><BR />This can be avoided, however, by passing {@code TRUE} to parameter
1147     * {@code 'createDirsIfNotExist'}.  If {@code 'outFileOrDirName'} specifies a directory (by
1148     * virtue of the fact that it ends with the {@code File.separator String}) - <I>and 
1149     * {@code 'createDirsIfNotExist'} is {@code TRUE}</I>, then this method will first create any
1150     * and all sub-directories needed using the standard Java's {@code File.mkdirs()} method before
1151     * copying the file.
1152     * 
1153     * <BR /><BR /><DIV CLASS=JDHint>
1154     * <B><SPAN STYLE="color: red;">Summary:</SPAN></B> The programmer may provide either a
1155     * Directory-Name or a File-Name to parameter {@code 'outFileOrDirName'}.  If a Directory-Name
1156     * was provided, the moved file will <B>keep its name</B> - having the same name as the
1157     * original file, ({@code 'inFileName'}) had (but have been copied to directory
1158     * {@code 'outFileOrDirName'}).
1159     * </DIV>
1160     *
1161     * <BR /><BR /><DIV CLASS=JDHintAlt>
1162     * <B STYLE="color: red;">Behavior Note:</B> The behavior of this copy operation is generally /
1163     * mostly the same as the standard {@code UNIX} or {@code MS-DOS} commands {@code 'cp'} and
1164     * {@code 'copy'} (respectively) - <B>differing only in this method's ability to have
1165     * directories created using {@code File.mkdirs()}</B>
1166     * </DIV>
1167     *
1168     * @param createDirsIfNotExist If the target output-file is situated in a directory-path that
1169     * does not exist, this method will throw an exception.  However, if this boolean parameter is
1170     * set to {@code TRUE} and the aforementioned situation occurs where the complete-directory
1171     * tree does not exist, then this method will first attempt to create the directories using
1172     * {@code java.io.File.mkdirs().}
1173     *
1174     * @throws SecurityException If boolean parameter {@code 'createDirsIfNotExist'} is
1175     * {@code TRUE} <I>and if</I> the directory named by parameter {@code 'outFileName'} does not
1176     * exist, <I>and if</I> attempting to create such a directory is not permitted by the
1177     * Operating-System, then this exception shall throw.
1178     *
1179     * @throws IOException For any number of fail-causes in reading or writing input stream data.
1180     * An explanation of all causes of such an operation is beyond the scope of this
1181     * method-documentation entry.
1182     *
1183     * @throws FileNotFoundException If the {@code 'inFileName'} is not found, or
1184     * {@code 'outFileOrDirName'} uses a directory path that doesn't exist on the File-System,
1185     * <B>and</B> parameter {@code 'createDirsIfNotExist'} is set to {@code FALSE}.
1186     * 
1187     * @throws SameSourceAndTargetException This exception will be thrown if the <CODE>Java Virtual
1188     * Machine</CODE> ascertains that the source and target locations point to the same physical
1189     * disk locations.  The classes utilized for this operation are from package
1190     * {@code java.nio.file.*};
1191     * 
1192     * @throws InvalidPathException If the <I>Java Virtual Machine</I> is unable to instantiate an
1193     * instance of {@code java.nio.files.Path} for either the {@code 'inFileName'} parameter or the
1194     * {@code 'outFileOrDirName'}, then this exception will be thrown.
1195     * 
1196     * @throws NoSuchFileException If, after instantiating an instance of {@code Path} for either
1197     * the {@code source} or the {@code target} locations, the <I>Java Virtual Machine</I> is
1198     * unable to build an instance of {@code Path} using the method {@code Path.toRealPath()}, then
1199     * this exception will throw.
1200     */
1201    public static void copyFile
1202        (String inFileName, String outFileOrDirName, boolean createDirsIfNotExist)
1203        throws IOException
1204    {
1205        File f = new File(outFileOrDirName);
1206
1207        if (createDirsIfNotExist) if (! f.exists()) f.mkdirs();
1208
1209        if (f.isDirectory())
1210        {
1211            if (! outFileOrDirName.endsWith(File.separator))
1212                outFileOrDirName = outFileOrDirName + File.separator;
1213
1214            outFileOrDirName = outFileOrDirName + StringParse.fromLastFileSeparatorPos(inFileName);
1215        }
1216
1217        String inPath = Paths.get(inFileName).toRealPath().toString();
1218        // throws InvalidPathException
1219        // throws NoSuchFileException
1220
1221        try
1222        {
1223            if (Paths.get(outFileOrDirName).toRealPath().toString().equals(inPath))
1224
1225                throw new SameSourceAndTargetException(
1226                    "The Source File Name and the Target Location provided to your copyFile " +
1227                    "request operation appear to point to the same physical-disk location:\n" +
1228                    inPath
1229                );
1230        }
1231
1232        catch (NoSuchFileException e) { }
1233
1234
1235        // NOTE: Mostly (but not always) the output file won't exist yet...  If it does not, 
1236        //       then we really don't need to worry about over-writing the origina file.
1237        // 
1238        // REMEMBER: The only purpose of the above test is to make sure that the source and
1239        //           target are not the same (to avoid clobbering the original file)
1240
1241        FileInputStream     fis     = new FileInputStream(inFileName);
1242        FileOutputStream    fos     = new FileOutputStream(outFileOrDirName);
1243        byte[]              b       = new byte[5000];
1244        int                 result  = 0;
1245
1246        try
1247            { while ((result = fis.read(b)) != -1) fos.write(b, 0, result); }
1248
1249        finally
1250            { fis.close();  fos.flush();  fos.close(); }
1251    }
1252
1253    /**
1254     * Convenience Method.
1255     * <BR />Invokes: {@code java.io.File.delete()}
1256     */
1257    public static void deleteFiles(String... fileNames)
1258    { for (String fileName : fileNames) (new File(fileName)).delete(); }
1259
1260    /**
1261     * Convenience Method.
1262     * <BR />Invokes: {@link #copyFile(String, String, boolean)}
1263     * <BR />And-Then: deletes
1264     */
1265    public static void moveFile
1266        (String inFileName, String outFileName, boolean createDirsIfNotExist)
1267        throws IOException
1268    {
1269        copyFile(inFileName, outFileName, createDirsIfNotExist);
1270        (new File(inFileName)).delete();
1271    }
1272
1273    /**
1274     * This deletes an entire directory, including any sub-directories.  It is like the UNIX
1275     * switch {@code -r} for the command {@code rm}, or the old Microsoft DOS Command
1276     * {@code 'deltree'} for deleting directories.  It simply reuses the class {@code FileTransfer}
1277     *
1278     * <BR /><BR /><B CLASS=JDDescLabel>Platform Independance (WORA):</B>
1279     * 
1280     * <BR />If this method is invoked from a UNIX or LINUX platform, then it will, generally,
1281     * bring about identical results as a call to {@link Shell#RM(String, String)} where the UNIX
1282     * {@code "-r"} (recursive) flag has been included / set.  On such a platform, an entire 
1283     * directory would be eliminated.
1284     * 
1285     * <BR /><BR />However, if executed from Windows, the class {@link Shell} would fail since it
1286     * only works on UNIX, but this method here would still succeed at its task.  It is a
1287     * "Platform-Independent" function.
1288     *
1289     * @param directoryName This should be a valid directory on the File-System.
1290     *
1291     * <BR /><BR /><DIV CLASS=JDHint>
1292     * <B STYLE="color: red">WARNING:</B> This command <B>does indeed delete the entire
1293     * Directory-Tree of the named directory!</B>
1294     * </DIV>
1295     * 
1296     * @param reCreateDirectoryOnExit This parameter allows the user to create an <I>an empty
1297     * directory with the same name</I> as the directory that was just deleted, after all of the
1298     * directory's contents have been deleted.  When this parameter is passed a value of
1299     * {@code TRUE}, the equivalent of the UNIX command {@code mkdir 'directoryName'} will be 
1300     * executed prior to exiting this method.
1301     * 
1302     * <BR /><BR />This can be a small convenience if the user desired that the directory be 
1303     * cleared, rather than deleted completely.
1304     *
1305     * @param log This parameter may be null, and if it is, it will be ignored.  This shall receive
1306     * textual log output from the deletion process.
1307     *
1308     * <EMBED CLASS='external-html' DATA-FILE-ID=APPENDABLE>
1309     *
1310     * @return This shall return a count on the total number of deleted files.  Note that when
1311     * directories are deleted (not files), their deletion <I>shall not count towards</I> the
1312     * total returned in this integer.
1313     *
1314     * @throws IllegalArgumentException Throws if the {@code String} provided to parameter
1315     * {@code directoryName} does not name a valid directory on the File-System.
1316     * 
1317     * @throws IOException May be thrown by the File-System Methods which underly this method.
1318     */
1319    public static int delTree
1320        (String directoryName, boolean reCreateDirectoryOnExit, Appendable log)
1321        throws IOException
1322    {
1323        if (directoryName == null) throw new NullPointerException
1324            ("You have provided null to parameter 'directoryName', but this is not allowed here.");
1325
1326        File f = new File(directoryName);
1327
1328        if (! f.exists()) throw new IllegalArgumentException(
1329            "The directory name you have provided: [" + directoryName + "] was not found on the " +
1330            "File System.  Aborted."
1331        );
1332
1333        if (! f.isDirectory()) throw new IllegalArgumentException(
1334            "The value you have provided to parameter 'directoryName' was: " + 
1335            "[" + directoryName + "], but unfortunately this is not the name of a directory on " +
1336            "the File System, but rather a file.  This is not allowed here."
1337        );
1338
1339        // Uses class FileNode to build the directory into Java Memory.
1340        // It is possibly of interest to note, that if running this java code on a UNIX or
1341        // LINUX platform, this method should perform the exact same operation as an invocation
1342        // of Shell.RM(directoryName, "-r");
1343
1344        FileNode fn = FileNode.createRoot(directoryName).loadTree();
1345
1346        int ret = FileTransfer.deleteFilesRecursive(fn, null, null, log);
1347
1348        if (reCreateDirectoryOnExit) f.mkdirs();
1349
1350        return ret;
1351    }
1352
1353    /**
1354     * This may read a Text-File containing integer data.  If this data is a <B>Comma Separated
1355     * Value</B> {@code 'CSV'} Text-File, please pass {@code TRUE} to the parameter
1356     * {@code 'isCSV'}.  If this file contains integers that have commas between digits in groups
1357     * of three (like {@code '30,000'}) please pass {@code TRUE} to the parameter
1358     * {@code 'hasCommasInInts'}.
1359     * 
1360     * <EMBED CLASS='external-html' DATA-TYPE=int DATA-FILE-ID=FRW_READNUM_FFORMAT>
1361     * 
1362     * <BR /><BR /><DIV CLASS=JDHint>
1363     * <B STYLE='color: red;'>Number-Format:</B> The numbers in this Text-File must be parse-able
1364     * by Java class {@code java.lang.Integer} via its method
1365     * {@code Integer.parseInt(String s, int radix)}
1366     * </DIV>
1367     * 
1368     * @param fileName This should contain the File-Name which itself contains a list of integers.
1369     * These integers may be separated by either a comma ({@code ','}) or a space ({@code ' '}).
1370     * 
1371     * @param hasCommasInInts It is allowed that the file named by {@code 'fileName'} contain
1372     * integers which use the commonly found notation of having a comma between groups of three
1373     * digits within an integer.  For instance the number {@code '98765'}, to a reader, is often
1374     * represented as {@code '98,765'}.  When this parameter is set to {@code TRUE}, this method
1375     * shall simply remove any comma that is found juxtaposed between two digits before 
1376     * processing any text found in the file.
1377     * 
1378     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1379     * file, then please pass {@code TRUE} to this parameter.  If {@code FALSE} is passed here,
1380     * then it is mandatory that the individual numbers inside the Text-File are separated by at
1381     * least one white-space character.
1382     * 
1383     * <BR /><BR /><DIV CLASS=JDHintAlt>
1384     * <B STYLE='color:red;'>Important:</B> If it is decided to set both of the boolean parameters
1385     * to {@code TRUE} - <I>where the integers have commas, <B STYLE="color: red">and</B> the
1386     * integers are separated by commas</I>, it is up to the programmer to ensure that the
1387     * individual numbers, themselves, are <I>not only</I> separated by a comma, <I>but also</I>
1388     * separated by a space as well.
1389     * </DIV>
1390     *
1391     * @param radix This is the {@code 'radix'}, which is also usually called the number's 
1392     * {@code 'base'} that is to be used when parsing the numbers.  Since Java's {@code class
1393     * java.lang.Integer} is used to perform the parse, <I>both</I> the {@code 'radix'}, <I>and</I>
1394     * the data found in the Text-File must conform to the Java method
1395     * {@code Integer.parseInt(String s, int radix)}.
1396     * 
1397     * <BR /><BR /><DIV CLASS=JDHint>
1398     * This parameter may not be ignored.  If the numbers in the Text-File are to be interpreted as
1399     * standard {@code 'decimal'} (<I>Base 10</I>) numbers, then the user should simply pass the
1400     * constant {@code '10'} to this parameter.
1401     * </DIV>
1402     * 
1403     * @return This method shall return a {@code java.util.stream.IntStream} consisting of the
1404     * integers that were found within the Text-File provided by {@code 'fileName'}.
1405     * 
1406     * <BR /><BR /><DIV CLASS=JDHintAlt>
1407     * An instance of {@code IntStream} is easily converted to an {@code int[]} array using the
1408     * method {@code IntStream.toArray()}.
1409     * </DIV>
1410     * 
1411     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1412     * found or not accessible in the File-System, then this exception will throw.
1413     * 
1414     * @throws IOException This exception throws if there are any errors that occur while
1415     * reading this file from the File-System.
1416     * 
1417     * @throws NumberFormatException If any of the numbers read from the Text-File are not
1418     * properly formatted, then this exception shall throw.
1419     * 
1420     * @see StringParse#NUMBER_COMMMA_REGEX
1421     * @see StringParse#COMMA_REGEX
1422     * @see StringParse#WHITE_SPACE_REGEX
1423     */
1424    public static IntStream readIntsFromFile
1425        (String fileName, boolean hasCommasInInts, boolean isCSV, int radix)
1426        throws FileNotFoundException, IOException
1427    {
1428        FileReader          fr  = new FileReader(fileName);
1429        BufferedReader      br  = new BufferedReader(fr);
1430        IntStream.Builder   b   = IntStream.builder();
1431        String              s   = "";
1432
1433        while ((s = br.readLine()) != null)
1434        {
1435            // Skip blank lines.
1436            if ((s = s.trim()).length() == 0) continue;
1437
1438            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1439            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1440            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1441
1442            if (hasCommasInInts)
1443                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1444
1445            String[] numbers = isCSV
1446                ? StringParse.COMMA_REGEX.split(s)
1447                : StringParse.WHITE_SPACE_REGEX.split(s);
1448 
1449            for (String number : numbers)
1450
1451                if ((number = number.trim()).length() > 0)
1452                    b.accept(Integer.parseInt(number, radix));
1453        }
1454
1455        br.close();
1456        fr.close();
1457        return b.build();
1458    }
1459
1460    /**
1461     * This may read a Text-File containing integer data.  If this data is a <B>Comma Separated
1462     * Value</B> {@code 'CSV'} Text-File, please pass {@code TRUE} to the parameter
1463     * {@code 'isCSV'}.  If this file contains integers that have commas between digits in groups
1464     * of three (like {@code '30,000'}) pleas pass {@code TRUE} to the parameter
1465     * {@code 'hasCommasInLongs'}.
1466     * 
1467     * <EMBED CLASS='external-html' DATA-TYPE=long DATA-FILE-ID=FRW_READNUM_FFORMAT>
1468     * 
1469     * <BR /><BR /><DIV CLASS=JDHint>
1470     * <B STYLE='color: red;'>Number-Format:</B> The numbers in this Text-File must be parse-able
1471     * by Java class {@code class java.lang.Long} using the method
1472     * {@code Long.parseLong(String s, int radix)}
1473     * </DIV>
1474     * 
1475     * @param fileName This should contain the File-Name which itself contains a list of 
1476     * {@code 'long'} integers.  These {@code long} integers may be separated by either a comma
1477     * ({@code ','}) or a space ({@code ' '}).
1478     * 
1479     * @param hasCommasInLongs It is allowed that the file named by {@code 'fileName'} contain
1480     * {@code long}-integers which use the commonly found notation of having a comma between groups
1481     * of three digits within a {@code long} integer.  For instance the number {@code '98765'}, to
1482     * a reader, is often represented as {@code '98,765'}.  When this parameter is set to
1483     * {@code TRUE}, this method shall simply remove any comma that is found juxtaposed between
1484     * two digits before processing any text found in the file.
1485     *
1486     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1487     * file, then please pass {@code TRUE} to this parameter.  If {@code FALSE} is passed here,
1488     * then it is mandatory that the individual numbers inside the Text-File are separated by at
1489     * least one white-space character.
1490     * 
1491     * <BR /><BR /><DIV CLASS=JDHintAlt>
1492     * <B STYLE='color: red'>Important:</B> If it is decided to set both of the boolean parameters
1493     * to {@code TRUE} - <I>where the {@code long} integers have commas,
1494     * <B STYLE='color: red'>and</B> the {@code long} integers are separated by commas</I>, it is
1495     * up to the programmer to ensure that the individual numbers, themselves, are <I>not only</I> 
1496     * separated by a comma, <I>but also</I> separated by a space as well.
1497     * </DIV>
1498     * 
1499     * @param radix This is the {@code 'radix'}, which is also usually called the number's 
1500     * {@code 'base'} that is to be used when parsing the numbers.  Since Java's {@code class
1501     * Long} is used to perform the parse, <I>both</I> the {@code 'radix'}, <I>and</I> the data
1502     * found in the Text-File must conform to the Java method
1503     * {@code Long.parseLong(String s, int radix)}.
1504     * 
1505     * <BR /><BR /><DIV CLASS=JDHint>
1506     * This parameter may not be ignored.  If the numbers in the Text-File are to be interpreted as
1507     * standard {@code 'decimal'} (<I>Base 10</I>) numbers, then the user should simply pass the
1508     * constant {@code '10'} to this parameter.
1509     * </DIV>
1510     * 
1511     * @return This method shall return a {@code java.util.stream.LongStream} consisting of the
1512     * {@code long}-integers that were found within the Text-File provided by {@code 'fileName'}.
1513     * 
1514     * <BR /><BR /><DIV CLASS=JDHintAlt>
1515     * An instance of {@code LongStream} is easily converted to a {@code long[]} array using the
1516     * method {@code LongStream.toArray()}.
1517     * </DIV>
1518     * 
1519     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1520     * found or not accessible in the File-System, then this exception will throw.
1521     * 
1522     * @throws IOException This exception throws if there are any errors that occur while
1523     * reading this file from the File-System.
1524     * 
1525     * @throws NumberFormatException If any of the numbers read from the Text-File are not
1526     * properly formatted, then this exception shall throw.
1527     * 
1528     * @see StringParse#NUMBER_COMMMA_REGEX
1529     * @see StringParse#COMMA_REGEX
1530     * @see StringParse#WHITE_SPACE_REGEX
1531     */
1532    public static LongStream readLongsFromFile
1533        (String fileName, boolean hasCommasInLongs, boolean isCSV, int radix)
1534        throws FileNotFoundException, IOException
1535    {
1536        FileReader          fr  = new FileReader(fileName);
1537        BufferedReader      br  = new BufferedReader(fr);
1538        LongStream.Builder  b   = LongStream.builder();
1539        String              s   = "";
1540
1541        while ((s = br.readLine()) != null)
1542        {
1543            // Skip blank lines.
1544            if ((s = s.trim()).length() == 0) continue;
1545
1546            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1547            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1548            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1549
1550            if (hasCommasInLongs)
1551                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1552
1553            String[] numbers = isCSV
1554                ? StringParse.COMMA_REGEX.split(s)
1555                : StringParse.WHITE_SPACE_REGEX.split(s);
1556 
1557            for (String number : numbers)
1558                if ((number = number.trim()).length() > 0)
1559                    b.accept(Long.parseLong(number, radix));
1560        }
1561
1562        br.close();
1563        fr.close();
1564        return b.build();
1565    }
1566
1567    /**
1568     * This may read a Text-File containing floating-point data.  If this data is a <B>Comma
1569     * Separated Value</B> {@code 'CSV'} Text-File, please pass {@code TRUE} to the parameter
1570     * {@code 'isCSV'}.  If this file contains {@code double's} that have commas between digits
1571     * in groups of three (like {@code '30,000,000,00'}) pleas pass {@code TRUE} to the parameter
1572     * {@code 'hasCommasInDoubles'}.
1573     *
1574     * <EMBED CLASS='external-html' DATA-TYPE=long DATA-FILE-ID=FRW_READNUM_FFORMAT>
1575     * 
1576     * <BR /><BR /><DIV CLASS=JDHint>
1577     * <B STYLE='color: red;'>Number-Format:</B> The numbers in this Text-File must be parse-able
1578     * by Java class {@code class java.lang.Double} using the method
1579     * {@code Double.parseDouble(String s)}
1580     * </DIV>
1581     * 
1582     * @param fileName This should contain the File-Name which itself contains a list of 
1583     * {@code 'double'} values.  These {@code double's} may be separated by either a comma
1584     * ({@code ','}) or a space ({@code ' '}).
1585     * 
1586     * @param hasCommasInDoubles It is allowed that the file named by {@code 'fileName'} contain
1587     * {@code double}-values which use the commonly found notation of having a comma between groups 
1588     * of three digits within a {@code double} value.  For instance the number {@code '98765.01'},
1589     * to a reader, can be represented as {@code '98,765.01'}.  When this parameter is set to
1590     * {@code TRUE}, this method shall simply remove any comma that is found juxtaposed between
1591     * two digits before processing any text found in the file.
1592     *
1593     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1594     * file, then please pass {@code TRUE} to this parameter.  If {@code FALSE} is passed here,
1595     * then it is mandatory that the individual numbers inside the Text-File are separated by at
1596     * least one white-space character.
1597     * 
1598     * <BR /><BR /><DIV CLASS=JDHintAlt>
1599     * <B STYLE="color: red">Important:</B> If it is decided to set both of the boolean parameters
1600     * to {@code TRUE} - <I>where the {@code double} values have commas,
1601     * <B STYLE="color: red">and</B> the {@code double} values are separated by commas</I>, it is
1602     * up to the programmer to ensure that the individual numbers, themselves, are <I>not only</I> 
1603     * separated by a comma, <I>but also</I> separated by a space as well.
1604     * </DIV>
1605     * 
1606     * @return This method shall return a {@code java.util.stream.DoubleStream} consisting of the
1607     * {@code double}-values that were found within the Text-File provided by {@code 'fileName'}.
1608     * 
1609     * <BR /><BR /><DIV CLASS=JDHint>
1610     * An instance of {@code DoubleStream} is easily converted to a {@code double[]} array using
1611     * the method {@code DoubleStream.toArray()}.
1612     * </DIV>
1613     * 
1614     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1615     * found or not accessible in the File-System, then this exception will throw.
1616     * 
1617     * @throws IOException This exception throws if there are any errors that occur while
1618     * reading this file from the File-System.
1619     * 
1620     * @throws NumberFormatException If any of the numbers read from the Text-File are not
1621     * properly formatted, then this exception shall throw.
1622     * 
1623     * @see StringParse#NUMBER_COMMMA_REGEX
1624     * @see StringParse#COMMA_REGEX
1625     * @see StringParse#WHITE_SPACE_REGEX
1626     */
1627    public static DoubleStream readDoublesFromFile
1628        (String fileName, boolean hasCommasInDoubles, boolean isCSV)
1629        throws FileNotFoundException, IOException
1630    {
1631        FileReader              fr  = new FileReader(fileName);
1632        BufferedReader          br  = new BufferedReader(fr);
1633        DoubleStream.Builder    b   = DoubleStream.builder();
1634        String                  s   = "";
1635
1636        while ((s = br.readLine()) != null)
1637        {
1638            // Skip blank lines.
1639            if ((s = s.trim()).length() == 0) continue;
1640
1641            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1642            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1643            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1644
1645            if (hasCommasInDoubles)
1646                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1647
1648            String[] numbers = isCSV
1649                ? StringParse.COMMA_REGEX.split(s)
1650                : StringParse.WHITE_SPACE_REGEX.split(s);
1651 
1652            for (String number : numbers)
1653
1654                if ((number = number.trim()).length() > 0)
1655                    b.accept(Double.parseDouble(number));
1656        }
1657
1658        br.close();
1659        fr.close();
1660        return b.build();
1661    }
1662
1663    /**
1664     * Convenience Method.
1665     * <BR />Invokes: {@code java.io.FileOutputStream.write(byte[])}.
1666     * <BR /><B>NOTE:</B> This may throw {@code IOException, FileNotFoundException}, etc...
1667     */
1668    public static void writeBinary(byte[] bArr, String fileName) throws IOException
1669    {
1670        // The input-stream is  'java.lang.AutoCloseable'.
1671        //
1672        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
1673        //       caught!  This also (potentially) throws SecurityException & FileNotFoundException
1674
1675        try
1676            (FileOutputStream fos = new FileOutputStream(fileName))
1677            { fos.write(bArr); }
1678    }
1679
1680
1681    /**
1682     * Convenience Method.
1683     * <BR />Invokes: {@code java.io.FileOutputStream.write(byte[], int, int)}.
1684     * <BR /><B>NOTE:</B> This may throw {@code IOException, IndexOutOfBoundsException}, etc...
1685     */
1686    public static void writeBinary(byte[] bArr, int offset, int len, String fileName)
1687        throws IOException
1688    {
1689        // The input-stream is  'java.lang.AutoCloseable'.
1690        //
1691        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
1692        //       caught!  This also (potentially) throws SecurityException, FileNotFoundException,
1693        //       IndexOutOfBoundsExcepton (if 'offset' or 'len' do not adhere to 'bArr' definition)
1694
1695        try
1696            (FileOutputStream fos = new FileOutputStream(fileName))
1697            { fos.write(bArr, offset, len); }
1698    }
1699
1700    /**
1701     * Convenience Method.
1702     * <BR />Invokes: {@link #readBinary(String, int, int)}.
1703     */
1704    public static byte[] readBinary(String fileName)
1705        throws FileNotFoundException, IOException
1706    { return readBinary(fileName, 0, -1); }
1707
1708    /**
1709     * Reads data from a binary file into a Java {@code byte[]} array.
1710     * 
1711     * <BR /><BR />Unlike Java's {@code FileOutputStream.write(...)} method, the {@code read(...)}
1712     * Java provides in that same exact class is more difficult to use.  This method is much longer
1713     * than its corresponding {@link #writeBinary(byte[], String)} method.
1714     * 
1715     * @param fileName The name of the file (on the File-System) to read.
1716     * 
1717     * @param offset The number of {@code byte's} to skip before appending a {@code byte} into the
1718     * output {@code byte[]} array.  If the value provided to parameer {@code 'offset'} is longer
1719     * than the size of the file itself, then a <B>zero-length {@code byte[]} array</B> will be
1720     * returned.
1721     * 
1722     * <BR /><BR /><DIV CLASS=JDHint>
1723     * The meaning of the value in parameter {@code 'offset'} is very different from the meaning of
1724     * a parameter by that exact same name, except in method {@code read(...)} of class
1725     * {@code FileInputStream}.  <B>HERE</B> the offset is the number of <I>bytes to skip inside of
1726     * file {@code 'fileName'}, before saving the values that are read froom disk.</I>  In the
1727     * {@code FileInputStream}, offset is used as an array pointer.
1728     * </DIV>
1729     * 
1730     * @param len Once the internal-loop has begun copying bytes from the Data-File into the
1731     * returned {@code byte[]} array, {@code byte's} will continue to be copied into this array
1732     * until precisely {@code 'len'} bytes have been copied.
1733     * 
1734     * <BR /><BR /><DIV CLASS=JDHintAlt>
1735     * The user may provide any negative number to this parameter, and the read process will simply
1736     * begin at position {@code 'offset'}, and continue reading until the End of the File has been
1737     * reached.
1738     * </DIV>
1739     * 
1740     * @return Returns a {@code byte[]}-array, with a length of (parameter) {@code 'len'} bytes.
1741     * 
1742     * @throws FileNotFoundException Class {@code java.io.FileInputStream} will throw a
1743     * {@code FileNotFoundException} if that class is passed a {@code 'fileName'} that does not
1744     * exist, or is a File-Name that represents a directory not a file.
1745     * 
1746     * @throws SecurityException If a security manager exists and its {@code checkRead} method
1747     * denies read access to the file.
1748     * 
1749     * @throws IOException The {@code FileInputStream} instance, and the {@code java.io.File}
1750     * instance are both capable of throwing {@code IOException}.
1751     * 
1752     * @throws IllegalArgumentException
1753     * 
1754     * <BR /><BR /><UL CLASS=JDUL>
1755     * <LI> The value provided to {@code 'offset'} is negative</LI>
1756     * <LI> The value provided to parameter {@code 'len'} is zero.</LI>
1757     * </UL>
1758     * 
1759     * @throws FileSizeException This exception throws if any of the following are detected:
1760     * 
1761     * <BR /><BR /><UL CLASS=JDUL>
1762     * 
1763     * <LI> The returned {@code byte[]} array cannot have a size larger than
1764     *      {@code Integer.MAX_VALUE}.  If the returned array would have a larger size - <I>based
1765     *      on any combination of provided input values</I>, then this exception will throw.
1766     *      <BR /><BR />
1767     *      </LI>
1768     * 
1769     * <LI> The value provided to {@code 'offset'} is larger than the size of the underlying
1770     *      file that is specified by parameter {@code 'fileName'}
1771     *      <BR /><BR />
1772     *      </LI>
1773     * 
1774     * <LI> The total of {@code offset + len} is larger than the size of the underlying file.
1775     *      <BR /><BR />
1776     *      </LI>
1777     * 
1778     * <LI> An invocation of the method {@code File.length()} returns zero, indicating that the
1779     *      file is either unreadable, non-existant, or possibly empty.
1780     *      </LI>
1781     * 
1782     * </UL>
1783     * 
1784     * @throws EOFException This particular exception should not be expected.  Before any reads are
1785     * done, the size of the Data-File is first checked to see if it is big enough to have the
1786     * amount of data that is requested by input paramters {@code 'offset'} and {@code 'len'}.  If
1787     * however, an error occurrs, and the Operating System returns an {@code EOF} earlier than 
1788     * expected (for unforseen reasons), then {@code EOFException} would throw.
1789     */
1790    public static byte[] readBinary(String fileName, int offset, int len)
1791        throws FileNotFoundException, IOException
1792    {
1793        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1794        // EXCEPTION CHECKS
1795        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1796
1797        if (offset < 0) throw new IllegalArgumentException
1798            ("The value of offset (" + offset + ") is negative.");
1799
1800        if (len == 0) throw new IllegalArgumentException
1801            ("A value of zero was provided to parameter 'len.'  This is not allowed");
1802
1803        File f = new File(fileName);
1804
1805        try
1806            (FileInputStream fis = new FileInputStream(f))
1807        {
1808            long fileLen = f.length();
1809
1810            // A file-name that points to a completely empty file was passed to parameter 'fileName'
1811            // **OR** the operating system returned 0 for some other error-related reason.
1812            // According to the Java-Doc explanation, a '0' returned value might mean there were
1813            // errors when trying to access the file.
1814
1815            if (fileLen == 0) throw new FileSizeException(
1816                "Calling java.io.File.length() returned 0 for the file-name that was " +
1817                "provided to this method:\n[" + fileName + "].  This might mean that the file " +
1818                "does not exist, or has other issues.",
1819                0
1820            );
1821
1822            // The file would end before we have even started reading bytes - after having skipped
1823            // 'offset' nmber of initial bytes.
1824
1825            if (offset > fileLen) throw new FileSizeException(
1826                "The value of offset (" + offset + ") is larger than the size of the binary " +
1827                "file, which only had size (" + fileLen + ").",
1828                fileLen
1829            );
1830
1831            // If 'len' was passed a negative value, that value is actually meaningless - and was
1832            // just used to indicate that reading the entire file starting at byte # 'offset' is
1833            // what the user is requesting.
1834
1835            if (len > 0)
1836
1837                // This simply checks how many bytes the file would need to have to provide for
1838                // reading from 'offset' up until 'offset + len'
1839
1840                if ((offset + len) > fileLen) throw new FileSizeException(
1841                    "The file [" + fileName + "] apparently has a size of [" + fileLen + "], " +
1842                    "but the offset (" + offset + ") and the length (" +  len + ") that was " +
1843                    "requested sum to (" + (offset+len) + "), which is greater than that size.",
1844                    fileLen
1845                );
1846
1847
1848            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1849            // More Exception Checks: Check what the size of the returned byte[] array will be
1850            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1851
1852            // Negative-Length means that bytes are read until an EOF occurrs
1853            if (len < 0)
1854            {
1855                long bytesToRead = fileLen - offset;
1856
1857                if (bytesToRead > Integer.MAX_VALUE) throw new FileSizeException(
1858                    "This file has [" + fileLen + "] bytes of data, and even with an offset of " +
1859                    '[' + offset + "], there are still " + bytesToRead + " bytes of data to " +
1860                    "place into the array.  This value is larger than Integer.MAX_VALUE " +
1861                    "(" + Integer.MAX_VALUE + ").",
1862                    fileLen
1863                );
1864
1865                else len = (int) bytesToRead;
1866            }
1867
1868            // A Positive length means that exactly the value inside input-parameter 'len' need to
1869            // be placed into the returned byte[] array.
1870
1871            else if (len > Integer.MAX_VALUE) throw new FileSizeException(
1872                "This file has [" + fileLen + "] bytes of data, which is larger than " +
1873                "Integer.MAX_VALUE (" + Integer.MAX_VALUE + ").",
1874                fileLen
1875            );
1876
1877
1878            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1879            // FIRST, if there is a NON-ZERO OFFSET, then exactly OFFSET bytes must be skipped
1880            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1881
1882            int     failedReads         = 0;
1883            long    bytesSkipped        = 0;
1884            int     totalBytesSkipped   = 0;
1885            int     bytesRemaining      = offset;
1886
1887            // NOTE: This loop is skipped, immediately / automatically, if indeed 'offset' is zero
1888            while (totalBytesSkipped < offset)
1889
1890                if ((bytesSkipped = fis.skip(bytesRemaining)) == 0)
1891                {
1892                    if (failedReads++ == 10) throw new IOException(
1893                        "There have 10 repeated attempts to read the file's data, but all " +
1894                        "attempts have resulted in empty-reads with zero bytes being  retrieved " +
1895                        "from disk."
1896                    );
1897                }
1898                else
1899                {
1900                    // I don't see why this should happen, but it will be left here, just in
1901                    // case Java screws up.
1902
1903                    if (bytesSkipped > bytesRemaining) throw new InternalError(
1904                        "This error is being thrown because the Java Virtual Machine has " +
1905                        "skipped past the end of the requested offset.  This has occured while " +
1906                        "calling FileInputStream.skip(offset)."
1907                    );
1908
1909                    // NOTE: I am *FULLY AWARE* this is redundant, but the variable names are
1910                    //       the only thing that is very readable about this method.
1911
1912                    totalBytesSkipped += bytesSkipped;
1913                    bytesRemaining -= bytesSkipped;
1914                }
1915
1916
1917            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1918            // THEN, Read the bytes from the file
1919            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1920
1921            byte[]  retArr      = new byte[len];
1922            int     arrPos      = 0;
1923
1924            // This one was already defined / declared in the previous part.  Initialize it, but
1925            // don't re-declare it.
1926            bytesRemaining = len;
1927
1928            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1929            // This loop exits when all (requested) bytes have been read, or EOFException!
1930            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1931
1932            while (bytesRemaining > 0)
1933            {
1934                int bytesRead = fis.read(retArr, arrPos, bytesRemaining);
1935
1936
1937                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1938                // Case 1: The EOF Marker was reached, before filling up the response-array
1939                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1940
1941                if (bytesRead == -1) if (bytesRemaining > 0) throw new EOFException(
1942                    "The end of file '" + fileName + "' was reached before " + len +
1943                    " bytes were read.  Only " + arrPos + " bytes were read."
1944                );
1945
1946
1947                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1948                // Yes, this seems redundant, but it's just the way it is
1949                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1950                // 
1951                // arrPos           ==>  0 ... retArr.length (retArr.length => input-param 'len')
1952                // bytesRemaining   ==>  'len' ... 0
1953
1954                arrPos          += bytesRead;
1955                bytesRemaining  -= bytesRead;
1956
1957
1958                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1959                // Case 2: Exactly the appropriate number of bytes were read.
1960                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1961
1962                if (arrPos == retArr.length) return retArr;
1963
1964
1965                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1966                // Case 3: This should not be reachable!
1967                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1968                //
1969                // Java's FileInputStream.read method is supposed to stop when it has read
1970                // 'bytesRemaining' number of bytes!
1971
1972                if (arrPos > retArr.length) throw new UnreachableError();
1973            }
1974
1975            // One of the cases inside the loop body must fire before the loop ever actually exits,
1976            // unless I'm missing something!
1977            throw new UnreachableError();
1978        }
1979    }
1980
1981    /**
1982     * Attempts to read a {@code java.lang.Class} from a class-file.
1983     * 
1984     * <BR /><BR />It is not neccessarily always knownst to the programmer what the
1985     * <I>Full-Package Class-Name</I>  of the {@code class} that's inside the class-file actually
1986     * is.
1987     * 
1988     * @param classFileName The name of any class-file on the File-System.
1989     * 
1990     * @param possibleClassNames If the exact <B STYLE='color: red;'>Full-Package Name</B> of the
1991     * class being read is known, then that ought to be the only {@code String} passed to this
1992     * Var-Args {@code String}-Parameter.  If there are multiple possibilities, pass all of them,
1993     * and all we be used in an attempt to parse &amp; load this class.
1994     * 
1995     * <BR /><BR /><B>DEVELOPER NOTE:</B> Perhaps I am stupid, but I cannot find any way (using the
1996     * standard JDK) to read a class file, and then ask that class-file either:
1997     * 
1998     * <BR /><BR /><UL CLASS=JDUL>
1999     * <LI>What the name of the {@code Class} in the Class-File is?</LI>
2000     * <LI>What the name of the {@code Package} being used in that Class-File is?</LI>
2001     * </UL>
2002     * 
2003     * <BR />So, for now, a Var-Args {@code String}-Array is required.
2004     * 
2005     * <BR /><BR />To add insult to injury, the standard Java Exception class
2006     * {@code 'TypeNotPresentException'} doesn't have a constructor that accepts a
2007     * {@code 'message'} parameter (it accepts a {@code 'typeName'}) parameter instead.  As a
2008     * result, if this method throws that exception, the error-message printed has a few 
2009     * 'extranneous characters' <B>BOTH</B> before the actual message, <B>AND</B> after it.  It is
2010     * staying this way because Java's Description of that exception matches precisely with its
2011     * use here.
2012     * 
2013     * @return An instance of {@code java.lang.Class} that was contained by the Class-File.
2014     * 
2015     * @throws IOException Java may throw several exceptions while attempting to load
2016     * the {@code '.class'} file into a {@code byte[]} array.  Such exceptions may include
2017     * {@code IOException}, {@code SecurityException}, {@code FileNotFoundException} etc...
2018     * 
2019     * @throws TypeNotPresentException If none of the names listed in Var-Args {@code String[]}
2020     * Array parameter {@code 'possibleClassNames'} are consistent with
2021     * <B STYLE='color:red'><I>BOTH</I></B> the package-name <B STYLE='color:red;'>AND</B> the
2022     * class-name of the actual class that resides inside {@code 'classFileName'}, then this 
2023     * exception will throw.
2024     * 
2025     * <BR /><BR /><DIV CLASS=JDHint>
2026     * <B STYLE='color:red'>Note:</B> Rather than simply returning 'null', this
2027     * {@code RuntimeException} is thrown as a nicely worded error message is provided.
2028     * </DIV>
2029     */
2030    public static Class<?> readClass(String classFileName, String... possibleClassNames)
2031        throws IOException
2032    {
2033        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2034        // Load the entire '.class' file into a byte[] array.
2035        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2036
2037        // Now read the bytes directly into a byte[] array, courtesy of Torello.Java.FileRW
2038        byte[] byteArray = readBinary(classFileName);
2039
2040
2041        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2042        // Create a ClassLoader - and convert "byte[]" into "Class<?> summarySorterClass"
2043        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2044
2045        // NOTE: The commented lines below will not work if there is a "Package Name" given to the
2046        //       class (in the '.java' file).  It is better to just read the raw class-file bytes 
2047        //       into a Java byte[] array.  SPECIFICALLY, if class is not inside of a directory
2048        //       that is PRECISELY-CONSISTENT with the full-path package+class name, then the class
2049        //       loader listed below will FAIL.  (NOTE: If the package+class name is consisten,
2050        //       then a programmer wouldn't need ANY OF THIS, and could simply call the normal
2051        //       "Class.forName(fullPackageClassName)" to get the class!)
2052        //
2053        // ClassLoader cl = new URLClassLoader(new URL[] { new URL("file://" + path) });
2054        // Class<?> ret = cl.loadClass(className);
2055        //
2056        // HOWEVER: Creating a new ClassLoader instance that accepts the byte-array (thereby
2057        //          exporting the 'protected' method defineClass) *DOES* work.
2058
2059        class ByteClassLoader extends ClassLoader
2060        {
2061            public Class<?> defineClass(String name, byte[] classBytes)
2062            { return defineClass(name, classBytes, 0, classBytes.length); }
2063        }
2064
2065        ByteClassLoader cl  = new ByteClassLoader();
2066        Class<?>        ret = null;
2067        StringBuilder   sb  = new StringBuilder();
2068
2069
2070        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2071        // Make attempts to convert the byte[] array into a java.lang.Class
2072        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2073
2074        for (String fullClassName : possibleClassNames)
2075
2076            try
2077                { return cl.defineClass(fullClassName, byteArray); }
2078
2079            catch (NoClassDefFoundError e)
2080            {
2081                String errorMessage = e.getMessage();
2082
2083                if (errorMessage != null) sb.append(errorMessage + '\n');
2084            }
2085
2086        throw new TypeNotPresentException(
2087            "The Class-File: [" + classFileName  + "] seems to have been successfully read, but " +
2088            "none of the user provided Canonical-Class Names (including a package) were " +
2089            "consistent with the actual name of the Class/Type inside that class file.  Below " +
2090            "are listed the exception-messages received, which include both the actual/complete " +
2091            "canonical class-name, along with your user-provided guesses.  Errors Saved:\n" +
2092            StrIndent.indent(sb.toString(), 4),
2093            null
2094        );
2095    }
2096}