001package Torello.JavaDoc.SyntaxHiLite;
002
003import Torello.Java.FileNode;
004import Torello.Java.StrPrint;
005import Torello.Java.FileRW;
006import Torello.Java.StorageWriter;
007
008// Needed for a JavaDoc Comment
009import Torello.Java.FileTransfer;
010
011import Torello.Java.CacheError;
012import Torello.Java.ToDoException;
013import Torello.JavaDoc.LinkJavaSource;
014
015import java.util.TreeSet;
016
017import java.io.File;
018
019/**
020 * A caching-system class that allows this tool to efficiently bypass calls to the server when
021 * an exact-copy of the hilited source-code already exists inside the cache.
022 * 
023 * <BR /><BR />
024 * <!-- <EMBED CLASS='external-html' DATA-FILE-ID=HLMC> -->
025 */
026public class HiLiteCache64
027{
028    // This is, as the name clearly says, the Cache-Directory
029    // 
030    // This is now, August of 2024, officially "Package-Private"
031    // 
032    // This is to allow ripping out some of the code from this class, and inserting it into 
033    // "Helper Classes".  The code in this class has always been very difficult to read, and
034    // creating separate, smaller, helper-classes makes it a lot easier.
035    // Unfortunately, this is now "Package-Private" instead of "Private".
036
037    final String cacheSaveDirectory;
038
039    // This is the list of Hash-Codes for all Code/HTML pairs stored in the cache.  This is the
040    // exact data-structure that is referred to as the "Master Hash File"
041    // 
042    // This has also been recently converted to "Package-Private", instead of "Private"
043    // See Above Note for Details.
044
045    final TreeSet<Long> hashCodes;
046
047    private static final short NUM_DIRS = 50;
048
049    /**
050     * Inform the user how much space (in bytes) is used by this {@code Cache}.
051     * @return The number of bytes being used on the file-system by this {@code Cache}.
052     */
053    public long totalSize()
054    {
055        return FileNode
056            .createRoot(this.cacheSaveDirectory)
057            .loadTree()
058            .getDirTotalContentsSize();
059    }
060
061    /**
062     * Count how many files and directories are contained in this {@code Cache}.
063     * @return The total number of files and sub-directories in the {@code Cache} directory.
064     */
065    public int totalNumber()
066    {
067        return FileNode
068            .createRoot(this.cacheSaveDirectory)
069            .loadTree()
070            .count();
071    }
072
073    /**
074     * This will load the hashCodes table to memory from the file-system directory identified
075     * by {@code String}-Parameter {@code 'cacheSaveDirectory'}.  An exception shall be thrown
076     * if this file is not found.
077     *
078     * @param cacheSaveDirectory This constructor presumes that this cache has been used and
079     * visited before.  This directory name should point to your local-cache of the
080     * {@code HiLite.ME} Server Code hilite past-operations.
081     *
082     * @throws CacheError This error will throw if the cache has not been instantiated, or
083     * is corrupted.  If the specified directory does not exist, then this {@code Error} shall
084     * also throw.  The chain-cause {@code Throwable} should be visible, and is included as the 
085     * {@code Throwable.getCause()}.
086     */
087    @LinkJavaSource(handle="CacheMethodsInternal64", name="checkCSD")
088    @LinkJavaSource(handle="CacheMethodsInternal64", name="checkTS")
089    public HiLiteCache64(String cacheSaveDirectory) throws CacheError
090    {
091        this.cacheSaveDirectory = CacheMethodsInternal64.checkCSD(cacheSaveDirectory);
092        this.hashCodes          = CacheMethodsInternal64.checkTS(this.cacheSaveDirectory);
093    }
094
095    /**
096     * This will save the hash-code {@code TreeSet<Integer>} to disk.  The <B>Master Hash-Code
097     * List</B> just keeps a record of the hashcodes of every {@code String} that was hilited
098     * by the Hiliter <I>(and therefore saved inside the Cache).</I>  This method will save
099     * that Java {@code TreeSet} of Hash-Codes to disk.
100     *
101     * @throws CacheError This {@code Error} will throw if there is a problem writing the
102     * master cache-hash to disk.  The chain-cause {@code Throwable} should be visible, and is
103     * included as the {@code Throwable.getCause()}
104     */
105    @LinkJavaSource(handle="CacheMethodsInternal64", name="persistMasterHashToDisk")
106    public void persistMasterHashToDisk() throws CacheError
107    { CacheMethodsInternal64.persistMasterHashToDisk(this); }
108
109    /**
110     * Will write this method soon.  It currently is not written.
111     * 
112     * <BR /><BR /><B CLASS=JDDescLabel>TO DO:</B>
113     * 
114     * <BR />        * 
115     * This is supposed to be for "Error Recovery".  Fortunately, an error has never really
116     * happend to me, and even if it did... Just deleting the whole thing and rebuilding
117     * the Cache by running the HiLiter on all of the files seems smarter/safer anyway.
118     * This has perpetually been on the "To Do List" for 5 years now...  I think it more
119     * prudent to remind people, just delete and start over is probably smarter, it your
120     * Cache directory got messed up (for whatever reason - but mine never has anyway!)
121     */
122    public void rebuildMasterHashCache()
123    { throw new ToDoException(); }
124
125    /**
126     * This will initialize a cache-file in the file-system directory identified by parameter
127     * {@code String cacheSaveDirectory}.  If the directory specified does not exist, a
128     * {@code CacheError} is thrown.  Any old cache files will be removed.  To attempt to
129     * preserve old cache-files, call method {@code initializeOrRepair(String, StorageWriter)}
130     * 
131     * <BR /><BR /><B><I>OrClear:</I></B> If the directory structure provided to this
132     * initialize method is not empty, the <SPAN STYLE="color: red;"><B><I>its entire contents
133     * shall be erased by a call to </I></B></SPAN> (Below)
134     * 
135     * <DIV CLASS=LOC>{@code 
136     * FileTransfer.deleteFilesRecursive
137     *     (FileNode.createRoot(cacheSaveDirectory).loadTree(), sw);
138     * }</DIV>
139     * 
140     * @param cacheSaveDirectory This constructor presumes that this cache has been used and
141     * visited before.  This directory name should point to your local-cache of 
142     * {@code HiLite.ME} Server Code hilite past-operations.
143     * 
144     * @param sw This receives log-writes from the call to
145     * {@link FileTransfer#deleteFilesRecursive} which clears the files currently in the cache.
146     * This parameter may be null, and if it is, output-text will be shunted.
147     * 
148     * @throws CacheError This exception will be throw if there are errors deleting any
149     * old-cache files currently in the directory; or if there is any error creating the new
150     * master hash-cache file.  The chain-cause {@code Throwable} should be visible, and is 
151     * included as the {@code Throwable.getCause()}.
152     */
153    @LinkJavaSource(handle="CacheMethodsInternal64", name="initializeOrClear")
154    public static HiLiteCache64 initializeOrClear(String cacheSaveDirectory, StorageWriter sw)
155        throws CacheError
156    { return CacheMethodsInternal64.initializeOrClear(cacheSaveDirectory, sw); }
157
158
159    // ********************************************************************************************
160    // ********************************************************************************************
161    // Package-Private Methods
162    // ********************************************************************************************
163    // ********************************************************************************************
164
165
166    String get(
167            final String    sourceCodeAsString,
168            final String    codeTypeParam,
169            final boolean   includeLineNumbers,
170            final byte      styleNum
171        )
172    {
173        final Long h = CacheMethodsInternal64.computeCacheKey
174            (codeTypeParam, includeLineNumbers, styleNum, sourceCodeAsString);
175
176        // NOTE: The Math.abs is OK, because it is just the directory name! (A little tricky)
177        if (! hashCodes.contains(h)) return null;
178
179        final String root = 
180            cacheSaveDirectory +
181            StrPrint.zeroPad((Math.abs(h.intValue()) % NUM_DIRS)) + 
182            File.separator + "H" + h.toString();
183
184
185        // Does a try-catch & re-throw as CacheError, with appropriate Error-Message
186        // If the file is not found - PRECISELY BECAUSE THIS WAS A CACHE-HIT => ...
187        //   ==> This would be a corrupted Cache.  The method below throws 'CacheError' if there 
188        //       are any problems reading this file.
189
190        final String saved = readFile(root + "-SOURCE.sdat");
191
192        // If the loaded Source-Code (from disk) - after a character for character comparison - is
193        // **EXACTLY EQUAL** to the Input / Requested Hi-Lite String, then the Hi-Lited Source-File
194        // is loaded from disk, and returned.
195        // 
196        // CASE 1:
197        // If there is a Cache-Hit, followed by a successful "Equals Comparison", but then the 
198        // HTML-File fails to load from disk - then (again) this must be a corrupted Cache and the
199        // method below will throw a Cache-Error if the file fails to load.
200        // 
201        // CASE 2;
202        // if the "Equals Comparison" fails - the the HTML-File is an outdated File.  This happens 
203        // if-and-when the Textual-Changes slipped by without modifying the String.hashCode() 
204        // computation.  This is why the "Equals Comparison is mandatory".  If the "equals" fails,
205        // then return null because the file will have to be re-hilited.
206
207        return saved.equals(sourceCodeAsString)
208            ? readFile(root + "-HILITE.sdat")
209            : null;
210    }
211
212    private static String readFile(final String fileName)
213    {
214        try
215            { return FileRW.readObjectFromFileNOCNFE(fileName, String.class, true); }
216
217        catch (Exception e)
218        {
219            throw new CacheError(
220                "There was an error reading a file from the cache-directory: " +
221                "    [" + fileName + "]\n" +
222                "Please see cause throwable.getCause() for more details",
223                e
224            );
225        }
226    }    
227    
228    void checkIn(
229            final String    sourceCodeAsString,
230            final String    hilitedCodeAsString, 
231            final String    codeTypeParam,
232            final boolean   includeLineNumbers,
233            final byte      styleNum
234        )
235    {
236        final Long h = CacheMethodsInternal64.computeCacheKey
237            (codeTypeParam, includeLineNumbers, styleNum, sourceCodeAsString);
238
239        // NOTE: The Math.abs is OK, because it is just the directory name! (A little tricky)
240        final String root = cacheSaveDirectory +
241            StrPrint.zeroPad((Math.abs(h.intValue()) % NUM_DIRS)) + File.separator;
242
243        try
244        {
245                File f = new File(root);
246                if (! f.exists()) f.mkdirs();
247        }
248
249        catch (Exception e)
250        {
251            throw new CacheError(
252                "There was an exception when accessing or creating the cache directory:\n" +
253                "    [" + root + "...sdat].\n" +
254                "See cause exception Throwable.getCause() for details.",
255                e
256            );
257        }
258
259        final String fileName = root + "H" + h.toString();
260
261        try
262        {
263            FileRW.writeObjectToFile(sourceCodeAsString,    fileName + "-SOURCE.sdat", true);
264            FileRW.writeObjectToFile(hilitedCodeAsString,   fileName + "-HILITE.sdat", true);
265        }
266
267        catch (Exception e)
268        {
269            throw new CacheError(
270                "There was an exception when writing to the cache directory:\n" +
271                "[" + root + "...sdat].\n" +
272                "Attempting to write Files:\n" + 
273                "    [" + fileName + "-SOURCE.sdat].\n" +
274                "    [" + fileName + "-HILITE.sdat].\n" +
275                "See cause exception Throwable.getCause() for details.",
276                e
277            );
278        }
279
280        hashCodes.add(h);
281        // DEBUGING System.out.println(" CHECKEDIN ");
282    }
283
284    public static void main(String[] argv)
285    {
286        if (argv.length != 1)
287        {
288            System.out.println(
289                "argv.length != 1\n" +
290                "argv[0] must contain the Cache-Directory Name.\n"
291            );
292
293            System.exit(1);
294        }
295
296        java.io.File f = new java.io.File(argv[0]);
297
298        if ((! f.exists()) || (! f.isDirectory()))
299        {
300            System.out.println(
301                "argv[0] must contain the Cache-Directory Name.\n" +
302                "Unfortunately: ((! f.exists()) || (! f.isDirectory()))\n"
303            );
304
305            System.exit(1);
306        }
307
308        initializeOrClear(argv[0], new StorageWriter());
309    }
310}