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