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
package Torello.JavaDoc.SyntaxHiLite;

import Torello.Java.FileNode;
import Torello.Java.StrPrint;
import Torello.Java.FileRW;
import Torello.Java.StorageWriter;

// Needed for a JavaDoc Comment
import Torello.Java.FileTransfer;

import Torello.Java.CacheError;
import Torello.Java.ToDoException;
import Torello.JavaDoc.LinkJavaSource;

import java.util.TreeSet;

import java.io.File;

/**
 * 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 class HiLiteCache64
{
    // This is, as the name clearly says, the Cache-Directory
    // 
    // This is now, August of 2024, officially "Package-Private"
    // 
    // This is to allow ripping out some of the code from this class, and inserting it into 
    // "Helper Classes".  The code in this class has always been very difficult to read, and
    // creating separate, smaller, helper-classes makes it a lot easier.
    // Unfortunately, this is now "Package-Private" instead of "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"
    // 
    // This has also been recently converted to "Package-Private", instead of "Private"
    // See Above Note for Details.

    final TreeSet<Long> hashCodes;

    private static final short NUM_DIRS = 50;

    /**
     * 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(this.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(this.cacheSaveDirectory)
            .loadTree()
            .count();
    }

    /**
     * This will load the hashCodes table to memory from the file-system directory identified
     * by {@code String}-Parameter {@code '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()}.
     */
    @LinkJavaSource(handle="CacheMethodsInternal64", name="checkCSD")
    @LinkJavaSource(handle="CacheMethodsInternal64", name="checkTS")
    public HiLiteCache64(String cacheSaveDirectory) throws CacheError
    {
        this.cacheSaveDirectory = CacheMethodsInternal64.checkCSD(cacheSaveDirectory);
        this.hashCodes          = CacheMethodsInternal64.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()}
     */
    @LinkJavaSource(handle="CacheMethodsInternal64", name="persistMasterHashToDisk")
    public void persistMasterHashToDisk() throws CacheError
    { CacheMethodsInternal64.persistMasterHashToDisk(this); }

    /**
     * Will write this method soon.  It currently is not written.
     * 
     * <BR /><BR /><B CLASS=JDDescLabel>TO DO:</B>
     * 
     * <BR />        * 
     * 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 5 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!)
     */
    public void rebuildMasterHashCache()
    { throw new ToDoException(); }

    /**
     * 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()}.
     */
    @LinkJavaSource(handle="CacheMethodsInternal64", name="initializeOrClear")
    public static HiLiteCache64 initializeOrClear(String cacheSaveDirectory, StorageWriter sw)
        throws CacheError
    { return CacheMethodsInternal64.initializeOrClear(cacheSaveDirectory, sw); }


    // ********************************************************************************************
    // ********************************************************************************************
    // Package-Private Methods
    // ********************************************************************************************
    // ********************************************************************************************


    String get(
            final String    sourceCodeAsString,
            final String    codeTypeParam,
            final boolean   includeLineNumbers,
            final byte      styleNum
        )
    {
        final Long h = CacheMethodsInternal64.computeCacheKey
            (codeTypeParam, includeLineNumbers, styleNum, sourceCodeAsString);

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

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


        // Does a try-catch & re-throw as CacheError, with appropriate Error-Message
        // If the file is not found - PRECISELY BECAUSE THIS WAS A CACHE-HIT => ...
        //   ==> This would be a corrupted Cache.  The method below throws 'CacheError' if there 
        //       are any problems reading this file.

        final String saved = readFile(root + "-SOURCE.sdat");

        // If the loaded Source-Code (from disk) - after a character for character comparison - is
        // **EXACTLY EQUAL** to the Input / Requested Hi-Lite String, then the Hi-Lited Source-File
        // is loaded from disk, and returned.
        // 
        // CASE 1:
        // If there is a Cache-Hit, followed by a successful "Equals Comparison", but then the 
        // HTML-File fails to load from disk - then (again) this must be a corrupted Cache and the
        // method below will throw a Cache-Error if the file fails to load.
        // 
        // CASE 2;
        // if the "Equals Comparison" fails - the the HTML-File is an outdated File.  This happens 
        // if-and-when the Textual-Changes slipped by without modifying the String.hashCode() 
        // computation.  This is why the "Equals Comparison is mandatory".  If the "equals" fails,
        // then return null because the file will have to be re-hilited.

        return saved.equals(sourceCodeAsString)
            ? readFile(root + "-HILITE.sdat")
            : null;
    }

    private static String readFile(final String fileName)
    {
        try
            { return FileRW.readObjectFromFileNOCNFE(fileName, String.class, true); }

        catch (Exception e)
        {
            throw new CacheError(
                "There was an error reading a file from the cache-directory: " +
                "    [" + fileName + "]\n" +
                "Please see cause throwable.getCause() for more details",
                e
            );
        }
    }    
    
    void checkIn(
            final String    sourceCodeAsString,
            final String    hilitedCodeAsString, 
            final String    codeTypeParam,
            final boolean   includeLineNumbers,
            final byte      styleNum
        )
    {
        final Long h = CacheMethodsInternal64.computeCacheKey
            (codeTypeParam, includeLineNumbers, styleNum, sourceCodeAsString);

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

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

        catch (Exception e)
        {
            throw new CacheError(
                "There was an exception when accessing or creating the cache directory:\n" +
                "    [" + root + "...sdat].\n" +
                "See cause exception Throwable.getCause() for details.",
                e
            );
        }

        final String fileName = root + "H" + h.toString();

        try
        {
            FileRW.writeObjectToFile(sourceCodeAsString,    fileName + "-SOURCE.sdat", true);
            FileRW.writeObjectToFile(hilitedCodeAsString,   fileName + "-HILITE.sdat", true);
        }

        catch (Exception e)
        {
            throw new CacheError(
                "There was an exception when writing to the cache directory:\n" +
                "[" + root + "...sdat].\n" +
                "Attempting to write Files:\n" + 
                "    [" + fileName + "-SOURCE.sdat].\n" +
                "    [" + fileName + "-HILITE.sdat].\n" +
                "See cause exception Throwable.getCause() for details.",
                e
            );
        }

        hashCodes.add(h);
        // DEBUGING System.out.println(" CHECKEDIN ");
    }

    public static void main(String[] argv)
    {
        if (argv.length != 1)
        {
            System.out.println(
                "argv.length != 1\n" +
                "argv[0] must contain the Cache-Directory Name.\n"
            );

            System.exit(1);
        }

        java.io.File f = new java.io.File(argv[0]);

        if ((! f.exists()) || (! f.isDirectory()))
        {
            System.out.println(
                "argv[0] must contain the Cache-Directory Name.\n" +
                "Unfortunately: ((! f.exists()) || (! f.isDirectory()))\n"
            );

            System.exit(1);
        }

        initializeOrClear(argv[0], new StorageWriter());
    }
}