1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
package Torello.Java;

import java.io.*;
import java.util.*;
import java.net.*;
import javax.net.ssl.*;

import java.nio.charset.Charset;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

import Torello.HTML.*;
import Torello.HTML.NodeSearch.*;

import Torello.Java.Shell.C;
import Torello.Java.FileNode;
import Torello.Java.Additional.RemoveUnsupportedIterator;
import Torello.Java.FileNode.RetTypeChoice;
import Torello.Java.Additional.Ret2;

/**
 * Wraps a basic on-line syntax-hiliter named <CODE>HiLite&#46;ME</CODE> (which, itself, wraps the
 * on-line hiliter <CODE>pygments&#46;org</CODE>).
 * 
 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=HILITEME>
 */
public class HiLiteMe
{
    // no constructors
    private HiLiteMe() { }

    // ********************************************************************************************
    // HiLite.ME parameter-tags stored internally.
    // ********************************************************************************************

    @SuppressWarnings("unchecked")
    private static final Vector<Object> dataFile = (Vector<Object>) LFEC.readObjectFromFile_JAR
        (Torello.Data.DataFileLoader.class, "data06.vdat", true, Vector.class);

    @SuppressWarnings("unchecked")
    private static final TreeMap<String, String> codeTypeDescriptions =
        (TreeMap<String, String>) dataFile.elementAt(0);

    @SuppressWarnings("unchecked")
    private static final Vector<String> styleTypes = (Vector<String>) dataFile.elementAt(1);

    // This is only loaded from disk if "SimplifySpans" method is invoked.  It is loaded only
    // once.  The only location where it can be loaded is inside 'simplifyColorSpans'
    // LAZY-LOADING
    private static TreeMap<String, TreeMap<String, TagNode>> allMaps = null;

    // This is only loaded from disk if the styleParamCSSClasses(String) method is called.
    // LAZY-LOADING
    private static TreeMap<String, String> styleCSSDefinitions = null;

    /**
     * Each time a piece of code is to be pretty-printed, {@code HiLite.ME} expects to receive a
     * "type of software" or "type of code" descriptor {@code String} that identifies what type of
     * textual-code it is receiving.  There are exactly 266 different types of software files that
     * may be passed to the {@code HiLite.ME} server.  These {@code String}-tag for these 
     * {@code 'Code Types'} may be viewed here.
     * 
     * <BR /><BR />Click the link below to see the complete list of programming-type codes.
     * <BR /><BR /><B><A HREF='doc-files/HiLiteMe/HiLiteProgrammingLanguages.html'>
     * Programming Language Codes</A></B>
     * 
     * @return An {@code Iterator<String>} that produces each {@code String}-tag that may be passed
     * as a {@code 'Code Type'} to {@code http://HiLite.ME}
     */
    public static Iterator<String> getCodeTypes()
    {
        // The 'RemoveUnsupportedIterator' wrapper class prohibits modifications to this TreeMap
        return new RemoveUnsupportedIterator<String>(codeTypeDescriptions.keySet().iterator());
    }

    /**
     * This will iterate over the full-name descriptions of the software types available for
     * parsing with the {@code HiLite.ME} server
     * 
     * @return An {@code Iterator<String>} that produces a {@code String}-description of each
     * software-types available for parsing.
     */
    public static Iterator<String> getCodeTypeDescriptions()
    {
        // The 'RemoveUnsupportedIterator' wrapper class prohibits modifications to this TreeMap
        return new RemoveUnsupportedIterator<String>(codeTypeDescriptions.values().iterator());
    }

    /**
     * This will iterate over the "Defining Style-Output Types" available to users of the
     * {@code http://HiLite.ME} server.
     * 
     * <BR /><BR />Click the link below to see the complete list of {@code 'Style Codes'}
     * <BR /><BR /><B><A HREF='doc-files/HiLiteMe/HiLiteStyleCodes.html'>
     * HiLiting Style Codes</A></B>
     * 
     * @return An {@code Iterator<String>} over the different available {@code String}-tags that
     * may be passed as a {@code 'Style Tag'} when performing a <B>"pretty print"</B> operation.
     */
    public static Iterator<String> getStyleTypes()
    {
        // The 'RemoveUnsupportedIterator' wrapper class prohibits modifications to this Vector
        return new RemoveUnsupportedIterator<String>(styleTypes.iterator());
    }

    /**
     * Returns the description for a specific {@code 'Code Type'}
     * @return the long-form of the {@code 'codeType'} as a java {@code String}
     */
    public static String getCodeDescription(String codeType)
    { return codeTypeDescriptions.get(codeType); }

    /**
     * Checks whether the passed {@code String}-parameter is a recognized {@code 'Code Type'}
     * 
     * <BR /><BR />Click the link below to see the complete list of programming-type codes.
     * <BR /><BR /><B><A HREF='doc-files/HiLiteMe/HiLiteProgrammingLanguages.html'>
     * Programming Language Codes</A></B>
     * 
     * @param s This may be any {@code java.lang.String}.  It is intended to be one of the listed
     * {@code 'Code Types'} available for use with the {@code HiLite.ME} server.
     * 
     * @return This will return <B>TRUE</B> if the passed {@code String}-tag is one of the tags 
     * listed with the {@code HiLite.ME} server for <I>Software Types</I>, or {@code 'Code Types'}
     * - and <B>FALSE</B> otherwise.  Use the {@code Iterator} to get the complete list of
     * available {@code 'Code Tags'} (or click the link, above, to view them in this browser).
     * 
     * @see #getCodeTypes()
     * @see #getCodeTypeDescriptions()
     */
    public static boolean isCodeType(String s)
    { return codeTypeDescriptions.containsKey(s); }

    /**
     * Checks whether the passed {@code String}-parameter is a recognized {@code 'Style Type'}
     * 
     * <BR /><BR />Click the link below to see the complete list of {@code 'Style Codes'}
     * <BR /><BR /><B><A HREF='doc-files/HiLiteMe/HiLiteStyleCodes.html'>
     * HiLiting Style Codes</A></B>
     * 
     * @param s This may be any {@code java.lang.String}.  It is intended to be one of the listed
     * {@code 'Style Types'} available for use with the {@code HiLite.ME} server.
     * 
     * @return This will return <B>TRUE</B> if the passed {@code String}-tag is one of the tags
     * listed with the {@code HiLite.ME} server for <I>Style Types</I>, and <B>FALSE</B> otherwise.
     * Use the {@code Iterator} to get the complete list of available {@code 'Style Tags'} (or
     * click the link, above, to view them in this browser).
     * 
     * @see #getStyleTypes()
     */
    public static boolean isStyleType(String s) { return styleTypes.contains(s); }


    // ********************************************************************************************
    // Here are the local variables and classes
    // ********************************************************************************************

    /**
     * If you choose to call the method {@code createIndex(TreeSet<String> fileIndexList, String
     * targetDirectory)}, then this {@code String} will be used as the header for that file.
     * 
     * <BR /><BR /><B>NOTE:</B> It may be changed, as it has not been declared {@code 'final'}.
     */
    public static String INDEX_HEADER_HTML =
        "<HTML>\n" +
        "<HEAD>\n" +
        "<TITLE>HiLite.ME Index</TITLE>\n" +
        "<META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n" +
        "<STYLE TYPE=\"text/css\">\n" +
        "A                      { color: black; text-decoration: none;  }\n" +
        "UL LI:Nth-Child(odd)   { background: lightgray;                }\n" +
        "UL LI:Nth-Child(even)  { background: white;                    }\n" +
        "UL LI                  { padding: 5 5 5 5;                     }\n" +
        "UL                     { max-width: 60%;                       }\n" +
        "</STYLE>\n" +
        "</HEAD>\n" +
        "<BODY>\n" +
        "<H2>HiLite.ME Code</H2>\n";

    // ********************************************************************************************
    // Here is class Params
    // ********************************************************************************************

    /**
     * Inner class for providing a list of parameters when hiliting multiple files on disk.
     * 
     * <BR /><BR />
     * <EMBED CLASS="external-html" DATA-FILE-ID="HLMP">
     */
    public static class Params
    {
        /**
         * The default value for {@code 'headerHTML'}  This value may be reset by accessing the
         * field {@code 'headerHTML'}, which is a {@code 'public'} field in this class.
         */
        public static final String DEFAULT_HEADER_HTML =
            "<HTML>\n<HEAD>\n<META http-equiv=\"Content-Type\" content=\"text/html; " +
            "charset=utf-8\" />\n<TITLE>INSERT HERE</TITLE>\n</HEAD>\n<BODY>\n";

        /**
         * The default value for {@code 'endingHTML'}  This value may be reset by accessing the
         * field {@code 'endingHTML'}, which is a {@code 'public'} field in this class.
         */
        public static final String DEFAULT_ENDING_HTML = "\n</BODY>\n</HTML>\n";
    
        /**
         * The style parameter to be used with {@code HiLite.ME}  View the list of available
         * {@code Style Tags} using {@link HiLiteMe#getStyleTypes()}
         */
        public String styleTag = "native";

        /**
         * When this is <B>TRUE</B>, {@code System.out} will print a line each time a file in the
         * {@code FileNode} tree is visited and sent to the pretty-printing HiLite servers.
         */
        public boolean verbose = true;

        /** 
         * This is the target directory for where the output {@code '.html'} files will be
         * sent.
         */
        public String targetDirectory = "";

        /**
         * This is the header HTML that is inserted above each post-processed / pretty-printed
         * source-code file that is received from {@code HiLite.ME} servers.  The default version
         * includes a {@code META-UTF8} clause, because often higher-level {@code Uni-Code}
         * characters from Mandarin Chinese, Spanish, Korean, Vietnamese and other foreign
         * languages are in (the writer, Torello's code).
         *
         * <BR /><BR /><B>NOTE:</B> The header must have a sub-string that says (in all caps)
         * {@code "INSERT HERE"} - the file name will be inserted.
         */
        public String headerHTML = DEFAULT_HEADER_HTML;

        /**
         * This is the HTML that is appended to each pretty-printed source-code file received from
         * {@code HiLite.ME} servers.  The default version of this {@code java.lang.String} simply
         * contains {@code <BODY>, </HTML>} - <I>change this if necessary!</I>.
         */
        public String endingHTML = DEFAULT_ENDING_HTML;

        /**
         * This will store each file that is successfully converted into a "Pretty Printed HTML"
         * file in the passed parameter {@code TreeSet}.  Java's {@code TreeSet} stores things
         * alphabetically, and does not allow duplicates.  It can be used to generate an
         * {@code 'index.html'} file.  Send this field to the
         * {@link HiLiteMe#createIndex(TreeSet, String)} method to build the {@code 'index.html'}
         * file.  If this is {@code null}, files that are pretty-printed by the
         * {@code http://HiLite.ME/} server will just not be logged to this data-structure.
         */
        public TreeSet<String> fileIndexList = new TreeSet<String>();

        /**
         * Any file that ends with {@code ".txt"} can be copied to the {@link #targetDirectory}
         * location - without being pretty printed if this variable is set to <B>TRUE</B>.
         */
        public boolean copyTextFiles = true;

        /**
         * This is null by default.  If it is not null, then each {@code java.lang.string 'key'} in
         * the Map should be a file ending, and the {@code 'value'} to which the key maps should be
         * a {@code Code Type} Tag that is recognized by the HiLite Pretty-Print HTML servers.
         *
         * <BR /><BR /><B>NOTE:</B> It is important to know that the {@code 'keys'} in this
         * {@code TreeMap} data-structure are valid file-name extensions, <I>that leave out the
         * leading '.' (period)</I>.  The key should be {@code 'java'} not {@code '.java'}.
         * Furthermore, the values in the data-structure to which these keys map, should be valid
         * HiLiteMe Pretty-Print Servers code-type values.  The complete list of valid
         * {@code 'Code Types'} can be viewed by iterating through {@code public Iterator<String>
         * getCodeTypes()}
         *
         * @see #getCodeTypes()
         * @see #isCodeType(String)
         * @see #deduceCodeType(String, TreeMap)
         */
        public TreeMap<String, String> customMappings = null;

        /**
         * Instantiate a {@code Cache}, and the {@code HiLite.ME} server will save all
         * code-{@code String's} into a cache.  For each source code request that is made <I>which
         * includes <B>BOTH</B> a {@code Params} parameter class, <B>AND</B> a non-null
         * {@code 'cache'} field</I>, the logic will first check the code-cache to see if an
         * identical {@code String} is in the cache.  If the {@code String} is identical, querying
         * the {@code HiLite.ME} server will be skipped, and the local copy used instead.  This can
         * increase build time many-fold, as seconds are reduced to milliseconds in large builds
         * where only 2 or 3 classes have seen code-changes since a previous build.
         * @see Cache
         */
        public Cache cache = null;

        /**
         * The public constructor has no body.  Modify the constants as necessary - since the
         * fields are all {@code 'public'}, and <I><B>are not {@code 'final'}</I></B>
         */
        public Params() { }
    }


    // ********************************************************************************************
    // Here is static inner class "Cache"
    // ********************************************************************************************

    /**
     * A caching-system class that allows this tool to efficiently bypass calls to the server when
     * an exact-copy of the hilited source-code already exists inside the cache.
     * 
     * <BR /><BR />
     * <EMBED CLASS="external-html" DATA-FILE-ID="HLMC">
     */
    public static class Cache
    {
        // This is, as the name clearly says, the Cache-Directory
        private final String cacheSaveDirectory;

        // This is the list of Hash-Codes for all Code/HTML pairs stored in the cache.  This is the
        // exact data-structure that is referred to as the "Master Hash File"

        private final TreeSet<Integer> hashCodes;

        private static final short  NUM_DIRS        = 50;
        private static final String HASH_SAVE_TREE  = "HILITED_STRINGS_HASH_CODE.ts";

        /**
         * Inform the user how much space (in bytes) is used by this {@code Cache}.
         * @return The number of bytes being used on the file-system by this {@code Cache}.
         */
        public long totalSize()
        { return FileNode.createRoot(cacheSaveDirectory).loadTree().getDirTotalContentsSize(); }

        /**
         * Count how many files and directories are contained in this {@code Cache}.
         * @return The total number of files and sub-directories in the {@code Cache} directory.
         */
        public int totalNumber()
        {return FileNode.createRoot(cacheSaveDirectory).loadTree().count(); }

        private static String checkCSD(String cacheSaveDirectory)
        {
            cacheSaveDirectory = cacheSaveDirectory.trim();

            if (! cacheSaveDirectory.endsWith(File.separator))
                cacheSaveDirectory = cacheSaveDirectory + File.separator;

            File f  = new File(cacheSaveDirectory);

            if (! f.exists()) throw new CacheError(
                "The specified cache-directory specified does not exist on the file-system: " +
                "[" + cacheSaveDirectory + "]"
            );

            return cacheSaveDirectory;
        }

        // the "return TreeSet<Integer>" complains about an unchecked cast.
        @SuppressWarnings("unchecked") 
        private static TreeSet<Integer> checkTS(String cacheSaveDirectory)
        {
            String  fName   = cacheSaveDirectory + HASH_SAVE_TREE;
            File    f       = new File(fName);

            if (! f.exists()) throw new CacheError(
                "The current-cache directory does not contain a primary-cache file: " +
                "[" + fName + "]"
            );

            Object o;

            try
                { o = FileRW.readObjectFromFile(fName, true); }

            catch (Throwable t)
            {
                throw new CacheError(
                    "There was an error attempting to read the following primary-cache file.  " + 
                    "It appears to be corrupted: [" + fName + "]",
                    t
                );
            }

            if (! (o instanceof TreeSet)) throw new CacheError(
                "There primary cache file loaded, but does not contain the correct " +
                "data-structure.  It appears to be corrupted. [" + fName + "]"
            );

            return (TreeSet<Integer>) o;
        }

        // Just saves a brand-new (empty) Hash-Code List (a java.util.TreeSet) to disk, using
        // Standard Java Object Serialization.
        private static TreeSet<Integer> writeNewTS(String cacheSaveDirectory)
        {
            TreeSet<Integer> hashCodes = new TreeSet<>();

            try
                { FileRW.writeObjectToFile(hashCodes, cacheSaveDirectory + HASH_SAVE_TREE, true); }

            catch (Throwable t)
            {
                throw new CacheError(
                    "There was an error writing the Cache Hash-Code File to disk.  " +
                    "[" + cacheSaveDirectory + HASH_SAVE_TREE + "].  " ,
                    t
                );
            }

            return hashCodes;
        }

        /**
         * This will load the hashCodes table to memory from the file-system directory identified
         * by parameter {@code String cacheSaveDirectory}.  An exception shall be thrown if this
         * file is not found.
         *
         * @param cacheSaveDirectory This constructor presumes that this cache has been used and
         * visited before.  This directory name should point to your local-cache of the
         * {@code HiLite.ME} Server Code hilite past-operations.
         *
         * @throws CacheError This error will throw if the cache has not been instantiated, or
         * is corrupted.  If the specified directory does not exist, then this {@code Error} shall
         * also throw.  The chain-cause {@code Throwable} should be visible, and is included as the 
         * {@code Throwable.getCause()}.
         */
        public Cache(String cacheSaveDirectory) throws CacheError
        {
            this.cacheSaveDirectory = checkCSD(cacheSaveDirectory);
            this.hashCodes          = checkTS(this.cacheSaveDirectory);
        }

        /**
         * This will save the hash-code {@code TreeSet<Integer>} to disk.  The <B>"Master Hash-Code
         * List"</B> just keeps a record of the hashcodes of every {@code String} that was hilited
         * by the Hiliter <I>(and therefore saved inside the Cache).</I>  This method will save
         * that Java {@code TreeSet} of Hash-Codes to disk.
         *
         * @throws CacheError This {@code Error} will throw if there is a problem writing the
         * master cache-hash to disk.  The chain-cause {@code Throwable} should be visible, and is
         * included as the {@code Throwable.getCause()}
         */
        public void persistMasterHashToDisk() throws CacheError
        {
            try
            {
                FileRW.writeObjectToFile
                    (hashCodes, this.cacheSaveDirectory + HASH_SAVE_TREE, true);
            } 
            catch (Throwable t)
            {
                throw new CacheError(
                    "There was an error writing the Master Hash-Code table to disk. " +
                    "File [" + this.cacheSaveDirectory + HASH_SAVE_TREE + "] was not saved. " +
                    "The cache-file will have to be refreshed at some point.  New Files " +
                    "Cache-Hash not saved.",
                    t
                );
            }
        }

        /** Will write this method soon.  It currently is not written. */
        public void rebuildMasterHashCache()
        {
            // TO DO
            // This is supposed to be for "Error Recovery".  Fortunately, an error has never really
            // happend to me, and even if it did... Just deleting the whole thing and rebuilding
            // the Cache by running the HiLiter on all of the files seems smarter/safer anyway.
            // This has perpetually been on the "To Do List" for 2 years now...  I think it more
            // prudent to remind people, just delete and start over is probably smarter, it your
            // Cache directory got messed up (for whatever reason - but mine never has anyway!)
        }

        /**
         * This will initialize a cache-file in the file-system directory identified by parameter
         * {@code String cacheSaveDirectory}.  If the directory specified does not exist, a
         * {@code CacheError} is thrown.  Any old cache files will be removed.  To attempt to
         * preserve old cache-files, call method {@code initializeOrRepair(String, StorageWriter)}
         * 
         * <BR /><BR /><B><I>OrClear:</I></B> If the directory structure provided to this
         * initialize method is not empty, the <SPAN STYLE="color: red;"><B><I>its entire contents
         * shall be erased by a call to </I></B></SPAN> (Below)
         * 
         * <DIV CLASS="LOC">{@code 
         * FileTransfer.deleteFilesRecursive
         *     (FileNode.createRoot(cacheSaveDirectory).loadTree(), sw);
         * }</DIV>
         * 
         * @param cacheSaveDirectory This constructor presumes that this cache has been used and
         * visited before.  This directory name should point to your local-cache of 
         * {@code HiLite.ME} Server Code hilite past-operations.
         * 
         * @param sw This receives log-writes from the call to
         * {@link FileTransfer#deleteFilesRecursive} which clears the files currently in the cache.
         * This parameter may be null, and if it is, output-text will be shunted.
         * 
         * @throws CacheError This exception will be throw if there are errors deleting any
         * old-cache files currently in the directory; or if there is any error creating the new
         * master hash-cache file.  The chain-cause {@code Throwable} should be visible, and is 
         * included as the {@code Throwable.getCause()}.
         */
        public static Cache initializeOrClear(String cacheSaveDirectory, StorageWriter sw)
            throws CacheError
        {
            cacheSaveDirectory = checkCSD(cacheSaveDirectory);

            final String tempStrForStupidLambdaFinal = cacheSaveDirectory;

            try
            {
                File f = new File(cacheSaveDirectory);

                if (f.isDirectory())
                    FileTransfer.deleteFilesRecursive(
                        FileNode.createRoot(cacheSaveDirectory).loadTree(), null,
                        (FileNode fn) -> fn.getFullPathName().equals(tempStrForStupidLambdaFinal),
                        sw
                    );

                f.mkdirs();
            }
            catch (Throwable t)
            {
                throw new CacheError(
                    "There was an error emptying/clearing the directory " +
                    "[" + cacheSaveDirectory + "] of it's contents, please see cause " +
                    "throwable.getCause() for details.",
                    t
                );
            }

            try
                { writeNewTS(cacheSaveDirectory); }

            catch (Throwable t)
            {
                throw new CacheError(
                    "There was an error saving/creating the new cache-file " +
                    "[" + cacheSaveDirectory + "], please see cause chain throwable.getCause(), " +
                    "for more details.",
                    t
                );
            }

            return new Cache(cacheSaveDirectory);
        }

        String get(
                String sourceCodeAsString, String codeTypeParam, String styleTypeParam,
                boolean includeLineNumbers
            )
        {
            Integer h = Integer.valueOf(
                codeTypeParam.hashCode() + styleTypeParam.hashCode() +
                (includeLineNumbers ? 1 : 0) +
                sourceCodeAsString.hashCode()
            );

            // NOTE: The Math.abs is OK, because it is just the directory name! (A little tricky)
            if (! hashCodes.contains(h)) return null;

            String root = 
                cacheSaveDirectory +
                StringParse.zeroPad((Math.abs(h.intValue()) % NUM_DIRS)) + 
                File.separator + "H" + h.toString();

            try
            {
                String saved = (String)
                    FileRW.readObjectFromFileNOCNFE(root + "-SOURCE.sdat", true);

                if (saved.equals(sourceCodeAsString))
                    return (String) FileRW.readObjectFromFileNOCNFE(root + "-HILITE.sdat", true);
                else
                    return null;
            }
            catch (Throwable t)
            {
                throw new CacheError(
                    "There was an error reading from the cache-directory: " +
                    "[" + root + "...sdat].  Please see cause throwable.getCause() for more " +
                    "details",
                    t
                );
            }
        }

        void checkIn(
                String sourceCodeAsString, String hilitedCodeAsString, 
                String codeTypeParam, String styleTypeParam, boolean includeLineNumbers
            )
        {
            Integer h = Integer.valueOf(
                codeTypeParam.hashCode() + styleTypeParam.hashCode() +
                (includeLineNumbers ? 1 : 0) +
                sourceCodeAsString.hashCode()
            );

            // NOTE: The Math.abs is OK, because it is just the directory name! (A little tricky)
            String root = cacheSaveDirectory +
                StringParse.zeroPad((Math.abs(h.intValue()) % NUM_DIRS)) + File.separator;

            try
            {
                File f = new File(root);
                if (! f.exists()) f.mkdirs();

                root = root + "H" + h.toString();

                FileRW.writeObjectToFile(sourceCodeAsString, root + "-SOURCE.sdat", true);
                FileRW.writeObjectToFile(hilitedCodeAsString, root + "-HILITE.sdat", true);

                hashCodes.add(h);
                // DEBUGING System.out.println(" CHECKEDIN ");
            }
            catch (Throwable t)
            {
                throw new CacheError(
                    "There was an exception when writing to the cache directory: " +
                    "[" + root + "...sdat].  See cause exception throwable.getCause(); " +
                    "for details.",
                    t
                );
            }
        }
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Recurse Source-Code Directory
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * This will generate an {@code 'index.html'} file of all the recently generated
     * {@code '.html'} files.
     * 
     * @param targetDirectory This is simply the save-location for this {@code 'index.html'} file
     * 
     * @param fileIndexList This must be a list of file-names that were generated by the 
     * {@code class HiLiteMe}
     */
    public static void createIndex(TreeSet<String> fileIndexList, String targetDirectory)
        throws IOException
    {
        StringBuilder out = new StringBuilder();

        out.append(INDEX_HEADER_HTML);
        out.append("<UL>");

        for (String f : fileIndexList) out.append(
            "<LI><A HREF=\"" + f + (f.endsWith(".txt") ? "" : ".html") +
            "\" TARGET=\"_blank\">\n" + f +"</A></LI>\n"
        );

        out.append("</UL>");
        out.append(Params.DEFAULT_ENDING_HTML);

        FileRW.writeFile(out, targetDirectory + "index.html");
    }

    /**
     * This will take a {@code FileNode} tree, and iterate through all of it - calling the
     * {@code http://HiLite.ME/} server for each software file that it finds / discovers and
     * recognizes.  Each source-code file that is within the {@code FileNode} tree that is passed,
     * after being <B>pretty-printed</B>, will be saved as a {@code '.html'} file in the
     * {@link Params#targetDirectory}
     *
     * <BR /><BR /><B STYLE="color: red;">IMPORTANT NOTE:</B> The provided java class
     * {@code FileNode} has some very simple filter operations for making sure that only the
     * code-files that you want to be transmitted will actually be sent. Each and every file in the
     * {@link FileNode} tree <I><B>whose {@code 'Code Type'} can be deduced</B></I> (by it's 
     * file-extension, or via the {@link Params#customMappings}) will be sent to the
     * {@code HiLite.ME} server for hiliting.   
     * 
     * <BR /><BR />It should be easy to call the {@code fileNodeRoot.prune(...)} method to make 
     * sure you are only transmitting/hiliting the files you want.
     * 
     * <BR /><BR /><B>FURTHERMORE:</B> The means by which files are copied from a "Source" 
     * source-code directory to a "Target" hilited-HTML directory is simply via the
     * {@code FileRW.copyFile} method.  Because the copy operations preserves the directory-tree
     * structure of the input source-code file-system tree, <B>it is imperative to use a relative
     * directory-location</B> when loading the source-code {@code FileNode} tree.
     * 
     * @param node This is the root node of a {@code FileNode} directory tree.  Every
     * operating-system file that is found  inside this tree will have it's code-hilited using the
     * {@code HiLite.Me} server.
     * 
     * @param hlmp Please review the inner-class {@code Params} to configure the extra parameters
     * for storing and saving the results of the code hiliting operation.
     * 
     * @see #prettyPrintScrape(String, String, String, boolean)
     * @see Params
     */
    public static void prettyPrintRecurseTree(FileNode node, Params hlmp) throws IOException
    {
        (new File(hlmp.targetDirectory + node.getFullPathName())).mkdirs();

        // This "flattens" the source-code FileNode tree into an Iterator.  The Iterator will not
        // iterate "FileNodes" - but rather String's - where each String is the relative path
        // name of the source code file.

        Iterator<String> files = node.getDirContentsFiles(RetTypeChoice.FULLPATH_ITERATOR);

        while (files.hasNext())
        {
            String fileName = files.next();

            if (hlmp.verbose) System.out.printf("%1$-80s", fileName);

            // Sometimes it helps to have any text-files copied to output / target directory
            if (hlmp.copyTextFiles && fileName.toLowerCase().endsWith(".txt"))
            {
                // Again, put this in the "Complete List" of files for the "creatIndex" method.
                // If the user wants an 'index.html' file that lists all of the files that have
                // been hilited, this list is necessary.

                if (hlmp.fileIndexList != null) hlmp.fileIndexList.add(fileName);

                if (hlmp.verbose)
                    System.out.println( "\nCOPYING " + C.BCYAN + "TEXT-FILE " + C.RESET);

                // This is why a "relative FileNode" is mandatory.  All that is happening is that
                // the "Full Path Name" of the source-code file is being appended to the
                // "Target Directory" name.
    
                FileRW.copyFile(fileName, hlmp.targetDirectory + fileName, true);

                // Text files are not actually hilited, just copied.
                continue;
            }

            // This checks the file-extension - '.java', '.js', '.html' - and uses that extension
            // String as a 'Code Type' with the HiLite server.
            //
            // If there are files where that system doesn't work - the mapping of a file-extension
            // to a 'Code Type' should be put into the "Custom Mappings" TreeMap in class
            // HiLiteMe.Params and passed to this 

            String codeTypeParam = deduceCodeType(fileName, hlmp.customMappings);

            if (codeTypeParam == null)
            {
                // Without a 'Code Type' there isn't any way to hilite the file...  Let the user
                // know and continue

                if (hlmp.verbose)
                    System.out.println(
                        "\nUNKNOWN FILE TYPE FOUND: " + C.BCYAN + fileName + C.RESET +
                        C.BYELLOW + "\nNOT CONVERTING FILE..." + C.RESET
                    );

                continue; // next source code file loop iteration
            }

            // Loads the source-code file to a String
            String fileText     = FileRW.loadFileToStringBuilder(fileName).toString();
            String hilitedCode  = null;

            // It is always faster to use a Cache with big projects...
            // If there is a 'Cache' check that first...

            if (hlmp.cache == null)

                // NOTE: The 'true' (last parameter) is passed to "includeLineNumbers"
                hilitedCode = prettyPrintScrape(fileText, codeTypeParam, hlmp.styleTag, true);

            else
            {
                // Check the Cache.  Note that it is pretty efficient, and doesn't do a 
                // character-by-character check - unless there is an exact hashCode match first.
                //
                // NOTE: The 'true' (last parameter) is passed to "includeLineNumbers"

                hilitedCode = hlmp.cache.get(fileText, codeTypeParam, hlmp.styleTag, true);

                // The Cache did not have an exact match for the Source Code File
                if (hilitedCode == null)
                {
                    // NOTE: The 'true' (last parameter) is passed to "includeLineNumbers"
                    //       This 'true' is the last parameter for both of the method calls below

                    hilitedCode = prettyPrintScrape(fileText, codeTypeParam, hlmp.styleTag, true);
                    hlmp.cache.checkIn(fileText, hilitedCode, codeTypeParam, hlmp.styleTag, true);
                }
            }

            // Now just write the hilited code to disk... Make sure to insert the HTML header
            // and tail/footer text.

            hilitedCode = hlmp.headerHTML.replace("INSERT HERE", fileName) + 
                hilitedCode + hlmp.endingHTML;

            FileRW.writeFile(hilitedCode, hlmp.targetDirectory + fileName + ".html");

            // Add this file-name to the list of files that have been converted/hilited
            // (but only if there is such a list being kep!)  This list can be used by the
            // method createIndex(TreeSet, String) to create an 'index.html' file

            if (hlmp.fileIndexList != null) hlmp.fileIndexList.add(fileName);

            // Let the user know this file has been successfully hilited.
            if (hlmp.verbose) System.out.println(C.BRED + "Completed." + C.RESET);
        }

        // After having iterated all the files in a directory, use recursion to iterate through the
        // files in any sub directories...
        //
        // The iterator of sub-dirs is an 'Iterator<FileNode>' rather than an 'Iterator<String>'
        // (which was used in the loop above)

        Iterator<FileNode> dirs = node.getDirContentsDirs(RetTypeChoice.ITERATOR);

        while (dirs.hasNext()) prettyPrintRecurseTree(dirs.next(), hlmp);
    }

    /**
     * Covenience Method.
     * <BR />Invokes: {@link #deduceCodeType(String, TreeMap)}
     */
    public static String deduceCodeType(String fileName)
    { return deduceCodeType(fileName, null); }

    /**
     * This attempts to guess the {@code 'Code Type'} parameter for the file, based on the input
     * file name. The file is loaded from disk, so be sure to include the full path to the file in
     * the {@code fileName}.
     * 
     * @param fileName The full-path name of a Source-Code / Software file.  It will be loaded from
     * disk, and transmitted to {@code HiLite.ME} servers and <B>"pretty-printed"</B> into HTML.
     * 
     * @param customMappings This parameter may be null, and if it is, then it is ignored.  If this
     * parameter is not null, then the file-extension obtained from the {@code String 'fileName'}
     * field will be "looked up" in the {@code TreeMap}, and if it matches one of these
     * custom-mappings of file-extension to {@code HiLite.ME} code-type parameters, that
     * {@code 'Code Type'} parameter will be used in the call to the Pretty Print Server.
     * 
     * @return the {@code 'Code Type'} that is associated with the file-name extension - or null if
     * there is no file-name extension, or the file-name extension is not found.
     */
    public static String deduceCodeType(String fileName, TreeMap<String, String> customMappings)
    {
        int extStartPos = fileName.lastIndexOf('.');

        if (extStartPos == -1) return null;

        String ext = fileName.substring(extStartPos + 1);

        if (ext == null) return null;

        if ((customMappings != null) && customMappings.containsKey(ext))
            return customMappings.get(ext);

        return isCodeType(ext) ? ext : null;
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Scrape / Pretty Print Methods
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Convenience Method.
     * <BR />Invokes: {@link #prettyPrintScrapeToVector(String, String, String, boolean)}
     * <BR />And: {@link Util#pageToString(Vector)}
     * <BR />Returns: HTML as a {@code java.lang.String}
     * <BR />No cache is expected.
     */
    public static String prettyPrintScrape
        (String codeText, String codeTypeParam, String styleTypeParam, boolean includeLineNumbers)
        throws IOException
    {
        // NOTE: Util.pageToString(...) accepts a 'Vector<HTMLNode>' and returns the input HTML
        //       as a java.lang.String

        return Util.pageToString(

            // NOTE: prettyPrintScrapeToVector Inserts HTML <SPAN> Elements into PLAIN-TEXT Source
            //       code.  Afterwards it converts the HTML from a 'String' to a 'Vector<HTMLNode>'

            prettyPrintScrapeToVector
                (codeText, codeTypeParam, styleTypeParam, includeLineNumbers)
        );
    }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #prettyPrintScrapeToVector(String, String, String, boolean, Cache)}
     * <BR />And: {@link Util#pageToString(Vector)}
     * <BR />Returns: HTML as a {@code java.lang.String}
     * <BR />Cache required.
     */
    public static String prettyPrintScrape(
            String codeText, String codeTypeParam, String styleTypeParam,
            boolean includeLineNumbers, Cache cache
        )
        throws IOException
    {
        // NOTE: Util.pageToString(...) accepts a 'Vector<HTMLNode>' and returns the input HTML
        //       as a java.lang.String

        return Util.pageToString(

            // NOTE: prettyPrintScrapeToVector Inserts HTML <SPAN> Elements into PLAIN-TEXT Source
            //       code.  Afterwards it converts the HTML from a 'String' to a 'Vector<HTMLNode>'
            //
            // ALSO: There are two versions of the method 'prettyPrintScrapeToVector.'  The one
            //       used here accepts and make-use-of a Cache.  In the method above, that version
            //       of this method DOES NOT utilize the Cache.

            prettyPrintScrapeToVector
                (codeText, codeTypeParam, styleTypeParam, includeLineNumbers, cache)
        );
    }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #prettyPrintScrapeToVector(String, String, String, boolean, Cache)}
     * <BR />And: {@link #simplifyColorSpans(Vector, String)}
     * <BR />And: {@link Util#pageToString(Vector)}
     * <BR />Returns: HTML as a {@code java.lang.String}
     * <BR />No cache is expected.
     */
    public static String prettyPrintScrapeAndSimplify
        (String codeText, String codeTypeParam, String styleTypeParam, boolean includeLineNumbers)
        throws IOException
    {
        // NOTE: Util.pageToString(...) accepts a 'Vector<HTMLNode>' and returns the input HTML
        //       as a java.lang.String

        return Util.pageToString(

            // The method 'simplifyColorSpans' modifies the HTML that is received from the HiLite
            // Server, and replaces each of the In-Line Style-Spans so that rather than containing
            // 'STYLE= ...' HTML Attributes, they contains a brief 'CLASS=...'.  This makes the
            // output HTML look cleaner, nicer, and easier to manage / modify.

            simplifyColorSpans(

                // NOTE: prettyPrintScrapeToVector Inserts HTML <SPAN> Elements into PLAIN-TEXT
                //       Source-Code.  Afterwards it converts the HTML from a 'String' to a
                //       'Vector<HTMLNode>'
    
                prettyPrintScrapeToVector
                    (codeText, codeTypeParam, styleTypeParam, includeLineNumbers),

                // simplifyColorSpans(...) needs to know which style-parameter was passed to the HiLite
                // server in order convert the STYLE Assignments into CSS Classes.
        
                styleTypeParam
            ));
    }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #prettyPrintScrapeToVectorAndSimplify(String, String, String, boolean, Cache)}
     * <BR />And: {@link Util#pageToString(Vector)}
     * <BR />Returns: HTML as a {@code java.lang.String}
     * <BR />Cache required.
     */
    public static String prettyPrintScrapeAndSimplify(
            String codeText, String codeTypeParam, String styleTypeParam,
            boolean includeLineNumbers, Cache cache
        )
        throws IOException
    {
        // NOTE: Util.pageToString(...) accepts a 'Vector<HTMLNode>' and returns the input HTML
        //       as a java.lang.String

        return Util.pageToString(

            // Method 'prettyPrintScrapeToVectorAndSimplify' does:
            //
            // ONE: Invokes the HiLite Server, and Converts the HTML-String which is returned by
            ///     that server (or found inside the Cache), and converts that HTML-String to a
            //      Vector<HTMLNode>
            //
            // TWO: Replaces all inline <SPAN STYLE='...'> elements to <SPAN CLASS='...'> in order
            //      to make the HTML nicer looking, more compact, and easier to control
 
            prettyPrintScrapeToVectorAndSimplify
                (codeText, codeTypeParam, styleTypeParam, includeLineNumbers, cache)
        );
    }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #prettyPrintScrapeToVector(String, String, String, boolean, Cache)}
     * <BR />And: {@link #simplifyColorSpans(Vector, String)}
     * <BR />Returns: {@code Vector<HTMLNode>}
     * <BR />No cache is expected.
     */
    public static Vector<HTMLNode> prettyPrintScrapeToVectorAndSimplify
        (String codeText, String codeTypeParam, String styleTypeParam, boolean includeLineNumbers)
        throws IOException
    {
        // The method 'simplifyColorSpans' modifies the HTML that is received from the HiLite
        // Server, and replaces each of the In-Line Style-Spans so that rather than containing
        // 'STYLE= ...' HTML Attributes, they contains a brief 'CLASS=...'.  This makes the
        // output HTML look cleaner, nicer, and easier to manage / modify.

        return simplifyColorSpans(

            // The method 'prettyPrintScrapeToVector' sends the 'codeText' to the HiLite Server,
            // and receives the HiLited Source-Code HTML.  Afterwards it simple converts the HTML
            // which it received as a java.lang.String *INTO* a Vector<HTMLNode>

            prettyPrintScrapeToVector
                (codeText, codeTypeParam, styleTypeParam, includeLineNumbers),

            // simplifyColorSpans(...) needs to know which style-parameter was passed to the HiLite
            // server in order convert the STYLE Assignments into CSS Classes.

            styleTypeParam
        );
    }

    /**
     * This will take a {@code java.lang.String} of code - in almost any coding language - and
     * generate an HTML-"ified" version of that code.  This is often called "Pretty-Printing" code.
     * The software-platform/engine that does the transformation of source code to "nice-looking
     * HTML code" is just a site called <A HREF="http://HiLite.me/" TARGET="_blank">
     * http://HiLite.me</A>.
     * 
     * <BR /><BR />This method takes three parameters, and the first is the code itself (passed as a
     * {@code java.lang.String.})  The second is the "descriptor" - which is a short
     * text-{@code String} that identifies what type (programming-language) is being passed to
     * {@code HiLite.ME}.  The complete list of available {@code Code Types} can be found in the 
     * Raw HTML of {@code HiLite.ME's} main page.
     * 
     * <BR /><BR /><UL CLASS="JDUL">
     *  <LI>{@code 'java'} =&gt; Java Code!</LI>
     *  <LI>{@code 'css'} =&gt; CSS - Cascading Style Sheets</LI>
     *  <LI>{@code 'js'} =&gt; Java-Script code</LI>
     *  <LI>The list contains at least 75 languages, select "View Code Source" in Google-Chrome or
     *      Internet-Explorer to see complete set of options!</LI>
     * </UL>
     * 
     * <BR /><BR />
     * The third parameter is a {@code 'style'} name / type parameter.  Again, view the
     * <A HREF="http://HiLite.ME" TARGET="_blank"> http://HiLite.ME</A> website to see the complete
     * list of available styles.  My preferred / defaults are {@code 'vim'} and {@code 'native'}
     *
     * @param codeText This is the software-code (like CSS, Java, SQL, Python, etc..) saved as a
     * single text-{@code String}
     * 
     * @param codeTypeParam <EMBED CLASS="external-html" DATA-FILE-ID=HLMCODETP>
     * 
     * @param styleTypeParam <EMBED CLASS="external-html" DATA-FILE-ID=HLMSTYLETP>
     * 
     * @param includeLineNumbers When this parameter receives <B>TRUE</B>, line-numbers are
     * appended to the HTML output.
     * 
     * @return The HTML is returned as a {@code Vector<HTMLNode>}<BR /><BR />
     * 
     * <EMBED CLASS="external-html" DATA-FILE-ID=HLMPPSRET>
     * 
     * @throws IOException If there are any problems communicating with the HiLite Server.
     * 
     * @see Torello.Java.URLs#toProperURLV2(String)
     * @see HTMLPage#getPageTokens(BufferedReader, boolean)
     * @see InnerTagGetInclusive
     * @see TagNodeRemoveInclusive
     */
    public static Vector<HTMLNode> prettyPrintScrapeToVector
        (String codeText, String codeTypeParam, String styleTypeParam, boolean includeLineNumbers)
        throws IOException
    {
        codeText = URLs.toProperURLV2(codeText);

        URL             url         = new URL("http://hilite.me");
        URLConnection   connection  = url.openConnection();

        connection.setDoOutput(true);  
        
        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");

        out.write(
            "lexer=" + codeTypeParam + 
            "&style=" + styleTypeParam + 
            (includeLineNumbers ? "&linenos=false" : "") +
            "&divstyles=border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;" +
            "&code=" + codeText 
        );
        out.flush();   

        BufferedReader br = new BufferedReader
            (new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));

        // Read the HTML String response and Parse the JTML into Vectorized-HTML.
        Vector<HTMLNode> v = HTMLPage.getPageTokens(br, false);

        // First, find the divider where the HiLited Code is located.  It still has some extra
        // bluff-and-bluster, fluff-and-fanfare, a bunch-of-crap to filter.

        // The response is inside of the <DIV ID=preview> ... </DIV>
        // However, there is another 'outer-div' that contains an Inline CSS Style Attribute. The
        // inner <DIV> ... </DIV> is the one to return.  If there are line-numbers, that inner
        // <DIV> will have a <TABLE> that has one row with two columns.  The first column will have
        // all of the line-numbers, and the second column will have the Hi-Lited Source Code.

        int idDIVPos = InnerTagFind.first(v, "div", "id", TextComparitor.EQ_CI, "preview");

        v = TagNodeGetInclusive.first(v, idDIVPos + 1, -1, "div");

        /*
        if (includeLineNumbers)
        {
            int end = v.size();
            System.out.println(
                "******* DEBUG *******\n" +
                Debug.print(v, 0, ((end < 20) ? end : 20), Debug::K) +
                "******* DEBUG *******\n" +
                Debug.print(v, ((end < 20) ? 0 : (end - 20)), end, Debug::K)
            );
            if (! Q.YN("continue?")) System.exit(0);
        }
        */

        return v;
    }

    /**
     * This performs an identical operation to method: {@code prettyPrintScrapeToVector(String,
     * String, String)}, but it checks the provided {@code cache} first, before querying the
     * server.
     * 
     * @param codeText This is the software-code (like CSS, Java, SQL, Python, etc..) saved as a
     * single text-{@code String}.
     * 
     * @param codeTypeParam <EMBED CLASS="external-html" DATA-FILE-ID=HLMCODETP>
     * 
     * @param styleTypeParam <EMBED CLASS="external-html" DATA-FILE-ID=HLMSTYLETP>
     * 
     * @param includeLineNumbers When this parameter receives <B>TRUE</B>, line-numbers are
     * appended to the HTML output.
     * 
     * @param cache This is the cache that must be passed.  Read about the {@link HiLiteMe.Cache}
     * {@code static inner class} for an explanation about how caching results from the server
     * works.  Save time on the build by caching results that have not changed.
     * 
     * @return The HTML is returned as a {@code Vector<HTMLNode>}<BR /><BR />
     * 
     * <EMBED CLASS="external-html" DATA-FILE-ID=HLMPPSRET>
     * 
     * @throws IOException If there are any problems communicating with the HiLite Server.
     * 
     * @see #prettyPrintScrapeToVector(String, String, String, boolean)
     * @see HTMLPage#getPageTokens(CharSequence, boolean)
     */
    public static Vector<HTMLNode> prettyPrintScrapeToVector(
            String codeText, String codeTypeParam, String styleTypeParam,
            boolean includeLineNumbers, Cache cache
        )
        throws IOException
    {
        // FIRST: Check the Cache, to see if the exact String has been hilited!
        String ret = cache.get(codeText, codeTypeParam, styleTypeParam, includeLineNumbers);

        // If there was a Cache hit -> return that String rather than querying the server.
        if (ret != null) return HTMLPage.getPageTokens(ret, false);

        // NO? Then query the server.
        Vector<HTMLNode> retVec = prettyPrintScrapeToVector
            (codeText, codeTypeParam, styleTypeParam, includeLineNumbers);

        // Make sure to save the response in the Cache for next time
        cache.checkIn(
            codeText, Util.pageToString(retVec), codeTypeParam, styleTypeParam,
            includeLineNumbers
        );

        return retVec;
    }

    /**
     * This performs an identical operation to method: {@code prettyPrintScrapeToVector(String,
     * String, String)}, but it checks the provided {@code cache} first, before querying the
     * server.  Furthermore, the HTML that is returned has been <B>simplified</B>, using the
     * method {@link #simplifyColorSpans(Vector, String)}.
     * 
     * <BR /><BR />Using this version of <B>Pretty Print</B> mandates that the user provide a
     * Cache.
     * 
     * @param codeText This is the software-code (like CSS, Java, SQL, Python, etc..) saved as a
     * single text-{@code String}.
     * 
     * @param codeTypeParam <EMBED CLASS="external-html" DATA-FILE-ID=HLMCODETP>
     * 
     * @param styleTypeParam <EMBED CLASS="external-html" DATA-FILE-ID=HLMSTYLETP>
     * 
     * @param includeLineNumbers When this parameter receives <B>TRUE</B>, line-numbers are
     * appended to the HTML output.
     * 
     * @param cache This is the cache that must be passed.  Read about the {@link HiLiteMe.Cache}
     * {@code static inner class} for an explanation about how caching results from the server
     * works.  Save time on the build by caching results that have not changed.
     * 
     * @return The HTML is returned as a {@code Vector<HTMLNode>}<BR /><BR />
     * 
     * <EMBED CLASS="external-html" DATA-FILE-ID=HLMPPSRET>
     * 
     * @throws IOException If there are any problems communicating with the HiLite Server.
     * 
     * @see #prettyPrintScrapeToVector(String, String, String, boolean)
     * @see HTMLPage#getPageTokens(CharSequence, boolean)
     * @see #simplifyColorSpans(Vector, String)
     */
    public static Vector<HTMLNode> prettyPrintScrapeToVectorAndSimplify(
            String codeText, String codeTypeParam, String styleTypeParam,
            boolean includeLineNumbers, Cache cache
        )
        throws IOException
    {
        // FIRST: Check the Cache, to see if the exact String has been hilited!
        String ret = cache.get(codeText, codeTypeParam, styleTypeParam, includeLineNumbers);

        // If there was a Cache hit -> return that String rather than querying the server.
        if (ret != null) return HTMLPage.getPageTokens(ret, false);

        // NO? Then query the server.
        Vector<HTMLNode> retVec =
            prettyPrintScrapeToVector(codeText, codeTypeParam, styleTypeParam, includeLineNumbers);

        // Replace all of the STYLE=... attributes to CLASS=... attributes
        simplifyColorSpans(retVec, styleTypeParam);

        // Make sure to save the response in the Cache for next time
        cache.checkIn(
            codeText, Util.pageToString(retVec), codeTypeParam, styleTypeParam,
            includeLineNumbers
        );

        return retVec;
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Simplify Color Spans
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Will return a {@code java.lang.String} containing all of the {@code CSS STYLE} definitions
     * for a particular {@code 'Style Type'}.
     * 
     * <BR /><BR /><B>NOTE:</B> These definitions are only useful if the HiLited Source Code that
     * you have used has been simplified from <B>in-line {@code 'STYLE=...'} attributes</B> into 
     * a <B>{@code CLASS=...}</B> version of the hiliting.
     * 
     * @param styleParam This may be any of the valid {@code 'Style Tags'} available for hiliting
     * source-code.  The complete list of {@code 'Style Tags'} may be viewed here:
     * 
     * <BR /><BR /><B><A HREF='doc-files/HiLiteMe/HiLiteStyleCodes.html'>
     * HiLiting Style Codes</A></B>
     * 
     * @return The {@code CSS Class} definitions as a {@code String}
     * 
     * <BR /><BR /><B>NOTE:</B> The {@code RETURN VALUES} (as {@code String's}) can actually be
     * viewed right here by clicking the link below.  Each {@code 'Style Type'} is followed by
     * a list of <B>CSS Definitions</B> that must be included in order for the Code HiLiting to
     * work (if-and-only-if) you have opted to use the "Simplified Color Spans."
     * 
     * <BR /><BR /><B><A HREF='doc-files/HiLiteMe/StyleTagCSSDefinitions.html'>
     * Style Tag CSS Definitions</A></B>
     * 
     * @throws IllegalArgumentException If an invalid {@code 'Style Type'} has been passed to the
     * {@code 'styleParam'} parameter.
     */
    @SuppressWarnings("unchecked")
    public static String styleParamCSSClasses(String styleParam)
    {
        if (! isStyleType(styleParam)) throw new IllegalArgumentException
            ("The Style Type Parameter passed [" + styleParam + "] is not a valid Style Type");

        if (styleCSSDefinitions == null)
            styleCSSDefinitions = (TreeMap<String, String>) LFEC.readObjectFromFile_JAR
                (Torello.Data.DataFileLoader.class, "data20.tmdat", true, TreeMap.class);

        return styleCSSDefinitions.get(styleParam);
    }

    /**
     * This may be called from the command line.  It will print the CSS Definitions for a 
     * {@code 'Style Type'} parameter.  The complete list of valid {@code Style Types} may be
     * viewed here:
     * 
     * <BR /><BR /><B><A HREF='doc-files/HiLiteMe/StyleTagCSSDefinitions.html'>
     * Style Tag CSS Definitions</A></B>
     */
    public static void main(String[] argv)
    {
        if (argv.length != 1)
            System.out.println("Pass a Style Type as an argument to this main method.");

        if (! isStyleType(argv[0]))
            System.out.println(
                "You have not passed a valid Style Type to this main method.  Please view " +
                "the documentation to see the list of valid Style Type's."
            );

        System.out.println(styleParamCSSClasses(argv[0]));
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=HLMSIMPLIFY>
     * 
     * @param page This should be Vectorized-HTML that was produced by the
     * {@code 'prettyPrintScrapeToVector'} method.
     *
     * @param styleTypeParam This has to be the same value for {@code 'styleTypeParam'} that was
     * used to convert the HTML for the {@code 'page'} parameter.  If a different
     * {@code 'Style Type'} is accidentally passed to this method, none of the HTML {@code <SPAN>}
     * elements will be replaced.
     * 
     * @return The original page {@code Vector<HTMLNode>}
     * 
     * @throws IllegalArgumentException If the passed {@code 'styleTypeParam'} is not recognized
     * by the internal list of available {@code 'Style Types'}.
     */
    @SuppressWarnings("unchecked")
    public static Vector<HTMLNode> simplifyColorSpans(Vector<HTMLNode> page, String styleTypeParam)
    {
        if (! isStyleType(styleTypeParam)) throw new IllegalArgumentException(
            "The passed 'styleTypeParam' value [" + styleTypeParam + "] is not a registered " +
            "Style Type Code known by this class."
        );

        // Lazy
        if (allMaps == null)
        {
            allMaps = 
                (TreeMap<String, TreeMap<String, TagNode>>)
                LFEC.readObjectFromFile_JAR
                (Torello.Data.DataFileLoader.class, "data19.tmdat", true, TreeMap.class);
        }

        // Retrieves the a TreeMap that maps STYLE-ATTRIBUTE values (retrieved from <SPAN>
        // elements) directly to replacement TagNode's that use a CLASS=... instead.

        TreeMap<String, TagNode> map = allMaps.get(styleTypeParam);

        // This should never happen, but if it does, this error is better than NullPointerException
        if (map == null) throw new InternalError(
            "The style parameter you have passed IS VALID, but unfortunately, the map file for " +
            "that style is not loading properly."
        );

        // Retrieve all HTML "<SPAN STYLE=...>" elements. Specifically, retrieve all <SPAN>
        // that actually contain a STYLE attribute.  Afterwards, retrieve all of the values of
        // that style-element, and store that in the 'styles' String-Array.

        int[]       spans       = InnerTagFind.all(page, "span", "style");
        String[]    styles      = Attributes.retrieve(page, spans, "style");
        TagNode     replacement = null;

        // Minor optimization that's inside the data-file.  The "STYLE='color: #123456;...'
        // eliminates the leading characters "color: #" in the style-attribute.  Here we need
        // to do the EXACT-SAME removal, or else the lookup-table will not find that
        // style-attribute.

        for (int i=0; i < styles.length; i++)
            if (styles[i].startsWith("color: #"))
                styles[i] = styles[i].substring("color: #".length());

        // Replace all of the <SPAN STYLE=...> TagNode's with the simplified TagNode's.

        for (int i=0; i < spans.length; i++)
            if ((replacement = map.get(styles[i])) != null)
                page.setElementAt(replacement, spans[i]);

        removeDuplicateColorSpans(page);

        return page;
    }

    private static final Pattern P = Pattern.compile("H(\\d{1,3})");

    // The HiLite.Me server has many places where a <SPAN>...</SPAN> of code is redundant, and 
    // should be removed.
    // It usually looks like the following (PAY CLOSE ATTENTION!)
    // 
    // <span style="color: #d0d0d0">removeArr</span> <span style="color: #d0d0d0">=</span>
    //
    // The above <SPAN> is FIRST simplified to (in the 'simplifyColorSpans') to:
    //
    // <span style=H10>removeArr</span> <span style=H10>=</span>
    // 
    // AND THEN SIMPLIFIED TO: (by this method)
    //
    // <SPAN STYLE=10>removeArr =</SPAN>
    private static int removeDuplicateColorSpans(Vector<HTMLNode> v)
    {
        HNLIInclusive       iter        = InnerTagInclusiveIterator.get(v, "span", "class", P);
        IntStream.Builder   b           = IntStream.builder();
        DotPair             prev        = new DotPair(0, v.size() - 1);  // Non-sense initialization
        int                 prevClass   = -1; // Non-sense initialization

        while (iter.hasNext())
        {
            DotPair         cur         = iter.nextDotPair();
            TagNode         tn          = (TagNode) v.elementAt(cur.start);
            int             curClass    = Integer.parseInt(tn.AV("class").substring(1));
            boolean         isClear     = prev.end < cur.start;
            HTMLNode        n;

            for (int i=(prev.end+1); isClear && (i < (cur.start-1)); i++)
                if (((n = v.elementAt(i)) instanceof TagNode) || (n.str.trim().length() > 0))
                    isClear = false;

            if (isClear && (curClass == prevClass))
            { b.accept(prev.end); b.accept(cur.start); }

            prev        = cur;
            prevClass   = curClass;
        }

        int[] removeArr = b.build().toArray();
        Util.removeNodesOPT(v, removeArr);

        return removeArr.length;
    }
}