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 HiLiteCache 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<Integer> 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="CacheMethodsInternal", name="checkCSD") 088 @LinkJavaSource(handle="CacheMethodsInternal", name="checkTS") 089 public HiLiteCache(String cacheSaveDirectory) throws CacheError 090 { 091 this.cacheSaveDirectory = CacheMethodsInternal.checkCSD(cacheSaveDirectory); 092 this.hashCodes = CacheMethodsInternal.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="CacheMethodsInternal", name="persistMasterHashToDisk") 106 public void persistMasterHashToDisk() throws CacheError 107 { CacheMethodsInternal.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="CacheMethodsInternal", name="initializeOrClear") 154 public static HiLiteCache initializeOrClear(String cacheSaveDirectory, StorageWriter sw) 155 throws CacheError 156 { return CacheMethodsInternal.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 // This is how the Hash-Code is computed. It is that simple. 174 final Integer h = Integer.valueOf( 175 codeTypeParam.hashCode() + 176 (includeLineNumbers ? 1 : 0) + 177 styleNum + 178 sourceCodeAsString.hashCode() 179 ); 180 181 // NOTE: The Math.abs is OK, because it is just the directory name! (A little tricky) 182 if (! hashCodes.contains(h)) return null; 183 184 final String root = 185 cacheSaveDirectory + 186 StrPrint.zeroPad((Math.abs(h.intValue()) % NUM_DIRS)) + 187 File.separator + "H" + h.toString(); 188 189 190 // Does a try-catch & re-throw as CacheError, with appropriate Error-Message 191 // If the file is not found - PRECISELY BECAUSE THIS WAS A CACHE-HIT => ... 192 // ==> This would be a corrupted Cache. The method below throws 'CacheError' if there 193 // are any problems reading this file. 194 195 final String saved = readFile(root + "-SOURCE.sdat"); 196 197 // If the loaded Source-Code (from disk) - after a character for character comparison - is 198 // **EXACTLY EQUAL** to the Input / Requested Hi-Lite String, then the Hi-Lited Source-File 199 // is loaded from disk, and returned. 200 // 201 // CASE 1: 202 // If there is a Cache-Hit, followed by a successful "Equals Comparison", but then the 203 // HTML-File fails to load from disk - then (again) this must be a corrupted Cache and the 204 // method below will throw a Cache-Error if the file fails to load. 205 // 206 // CASE 2; 207 // if the "Equals Comparison" fails - the the HTML-File is an outdated File. This happens 208 // if-and-when the Textual-Changes slipped by without modifying the String.hashCode() 209 // computation. This is why the "Equals Comparison is mandatory". If the "equals" fails, 210 // then return null because the file will have to be re-hilited. 211 212 return saved.equals(sourceCodeAsString) 213 ? readFile(root + "-HILITE.sdat") 214 : null; 215 } 216 217 private static String readFile(final String fileName) 218 { 219 try 220 { return FileRW.readObjectFromFileNOCNFE(fileName, String.class, true); } 221 222 catch (Exception e) 223 { 224 throw new CacheError( 225 "There was an error reading a file from the cache-directory: " + 226 " [" + fileName + "]\n" + 227 "Please see cause throwable.getCause() for more details", 228 e 229 ); 230 } 231 } 232 233 void checkIn( 234 final String sourceCodeAsString, 235 final String hilitedCodeAsString, 236 final String codeTypeParam, 237 final boolean includeLineNumbers, 238 final byte styleNum 239 ) 240 { 241 final Integer h = Integer.valueOf( 242 codeTypeParam.hashCode() + 243 (includeLineNumbers ? 1 : 0) + 244 styleNum + 245 sourceCodeAsString.hashCode() 246 ); 247 248 // NOTE: The Math.abs is OK, because it is just the directory name! (A little tricky) 249 final String root = cacheSaveDirectory + 250 StrPrint.zeroPad((Math.abs(h.intValue()) % NUM_DIRS)) + File.separator; 251 252 try 253 { 254 File f = new File(root); 255 if (! f.exists()) f.mkdirs(); 256 } 257 258 catch (Exception e) 259 { 260 throw new CacheError( 261 "There was an exception when accessing or creating the cache directory:\n" + 262 " [" + root + "...sdat].\n" + 263 "See cause exception Throwable.getCause() for details.", 264 e 265 ); 266 } 267 268 final String fileName = root + "H" + h.toString(); 269 270 try 271 { 272 FileRW.writeObjectToFile(sourceCodeAsString, fileName + "-SOURCE.sdat", true); 273 FileRW.writeObjectToFile(hilitedCodeAsString, fileName + "-HILITE.sdat", true); 274 } 275 276 catch (Exception e) 277 { 278 throw new CacheError( 279 "There was an exception when writing to the cache directory:\n" + 280 "[" + root + "...sdat].\n" + 281 "Attempting to write Files:\n" + 282 " [" + fileName + "-SOURCE.sdat].\n" + 283 " [" + fileName + "-HILITE.sdat].\n" + 284 "See cause exception Throwable.getCause() for details.", 285 e 286 ); 287 } 288 289 hashCodes.add(h); 290 // DEBUGING System.out.println(" CHECKEDIN "); 291 } 292 293 public static void main(String[] argv) 294 { 295 if (argv.length != 1) 296 { 297 System.out.println( 298 "argv.length != 1\n" + 299 "argv[0] must contain the Cache-Directory Name.\n" 300 ); 301 302 System.exit(1); 303 } 304 305 java.io.File f = new java.io.File(argv[0]); 306 307 if ((! f.exists()) || (! f.isDirectory())) 308 { 309 System.out.println( 310 "argv[0] must contain the Cache-Directory Name.\n" + 311 "Unfortunately: ((! f.exists()) || (! f.isDirectory()))\n" 312 ); 313 314 System.exit(1); 315 } 316 317 initializeOrClear(argv[0], new StorageWriter()); 318 } 319}