001package Torello.Java.Build; 002 003import Torello.Java.Shell; 004import Torello.Java.OSResponse; 005import Torello.Java.OSExtras; 006import Torello.BuildJAR.Build; 007import Torello.Java.FileNode; 008import Torello.Java.StrCmpr; 009import Torello.Java.StrFilter; 010import Torello.Java.FileRW; 011import Torello.Java.RTC; 012 013import Torello.Java.Additional.BiAppendable; 014import Torello.Java.Additional.AppendableSafe; 015import Torello.Java.ReadOnly.ROArrayListBuilder; 016import Torello.Java.ReadOnly.ReadOnlyList; 017 018import static Torello.Java.C.*; 019 020import java.io.IOException; 021import java.io.File; 022import java.io.FilenameFilter; 023import java.io.FileFilter; 024 025import java.util.TreeMap; 026import java.util.stream.Stream; 027 028/** 029 * This is the fourth Build-Stage, and it runs the UNIX-Shell utility {@code 'tar'} and the Java 030 * utility {@code 'jar'}. These operations are performed via {@link Shell Torello.Java.Shell}. 031 * 032 * <EMBED CLASS='external-html' DATA-FILE-ID=S04_TAR_JAR> 033 */ 034@Torello.JavaDoc.StaticFunctional 035public class S04_TarJar 036{ 037 // Completely irrelevant, and the 'private' modifier keeps it off of JavaDoc 038 private S04_TarJar() { } 039 040 private static final String FS = File.separator; 041 042 043 // ******************************************************************************************** 044 // ******************************************************************************************** 045 // This class MAIN METHOD 046 // ******************************************************************************************** 047 // ******************************************************************************************** 048 049 050 public static void compress(Builder builder) throws IOException 051 { 052 builder.timers.startStage04(); 053 054 final StringBuilder SB_TAR = new StringBuilder(); 055 final StringBuilder SB_JAR = new StringBuilder(); 056 057 Printing.startStep(4); 058 059 if (! builder.cli.JAR_ONLY) 060 { 061 twoTarFiles(builder, SB_TAR); 062 063 System.out.println(); 064 } 065 066 jarFile(builder, SB_JAR); 067 068 if (! builder.cli.JAR_ONLY) 069 builder.logs.write_S04_LOGS(SB_TAR.toString(), SB_JAR.toString()); 070 071 if (builder.cli.JAR_ONLY && (builder.JAR_FILE_NAME != null)) 072 { 073 System.out.println( 074 "Moving " + BYELLOW + builder.JAR_FILE + RESET + " to " + 075 BYELLOW + builder.JAR_FILE_NAME + RESET + '\n' 076 ); 077 078 FileRW.moveFile(builder.JAR_FILE, builder.JAR_FILE_NAME, false); 079 } 080 081 builder.timers.endStage04(); 082 } 083 084 085 // ******************************************************************************************** 086 // ******************************************************************************************** 087 // Build the 2 TAR-Files, Build the 1 JAR-File 088 // ******************************************************************************************** 089 // ******************************************************************************************** 090 091 092 private static void twoTarFiles 093 (final Builder builder, final StringBuilder SB_TAR) 094 throws IOException 095 { 096 // NOTE: It is OK to print the entire command directly to standard output, because the 097 // '.tar' Files are very short commands, that only tar-up a single directory 098 // 099 // MAIN-TAR ==> Root-Source 100 // JAVA-DOC-TAR ==> 'javadoc/' 101 // 102 // Shell-Constructor Paramters used: 103 // (outputAppendable, commandStrAppendable, standardOutput, errorOutput) 104 105 Shell shell = new Shell(SB_TAR, new BiAppendable(SB_TAR, System.out), null, null); 106 107 // JavaHTML-1.x.tar.gz 108 CHECK( 109 shell.COMMAND( 110 "tar", 111 new String[] { "-cvzf", builder.TAR_FILE, builder.TAR_SOURCE_DIR } 112 )); 113 114 SB_TAR.append(Printing.TAR_JAR_DIVIDER); 115 116 // JavaHTML-javadoc-1.x.tar 117 CHECK( 118 shell.COMMAND( 119 "tar", 120 new String[] { "-cvf", builder.JAVADOC_TAR_FILE, builder.LOCAL_JAVADOC_DIR } 121 )); 122 } 123 124 125 // ******************************************************************************************** 126 // ******************************************************************************************** 127 // Build the JAR-File 128 // ******************************************************************************************** 129 // ******************************************************************************************** 130 131 132 private static void jarFile(final Builder builder, final StringBuilder logOnly) 133 throws IOException 134 { 135 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 136 // Setup some variables / constants for running this class 137 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 138 139 // Uses Shell-Contructor: 140 // (outputAppendable, commandStrAppendable, standardOutput, errorOutput) 141 142 final Shell shell = new Shell(logOnly, logOnly, null, null); 143 144 final AppendableSafe logAndScreen = new AppendableSafe( 145 new BiAppendable(System.out, logOnly), 146 AppendableSafe.USE_APPENDABLE_ERROR 147 ); 148 149 // The first loop-iteration uses a different command-switch to the 'jar' command 150 // *** First jar-command uses: "-cvf" (Create, Verbose, Files) 151 // *** Successive jar-commands use: "-uvf" (Update, Verbose, Files) 152 153 boolean firstIteration = true; 154 155 // The Shell 'jar' command must be executed from the File-System directory set to the 156 // "Current Working Directory" - or else the location inside the '.jar' will just be all 157 // messed up! 158 // 159 // NOTE: This cute little thing here was A LOT of work, not just a little bit. The 160 // "Relative Path" within the jar cannot include extranneous class-path directory 161 // stuff. In the end, using it just 3 lines of code! 162 163 final OSExtras osExtras = new OSExtras(); 164 165 // This creates a 'FileFilter' that is **ALSO** to configured to print out directories that 166 // match - into the provided StringBuilder - 'logAndScreen' 167 // 168 // Prior to printing out the directories it accepts, "getSubPackageDirFilter" was a private 169 // static final constant named "PACKAGE_DIR_FILTER" (or something like that - it wasn't a 170 // method is the only point). 171 172 final FileFilter JAR_SUB_PACKAGES_DIR_FILTER = getSubPackageDirFilter(logAndScreen); 173 174 // Since the Shell & OSExtras combo is being used - where the command is actually executed 175 // from a separate directory, the Jar-File needs to be referenced using its absolute path 176 // name 177 178 final String JAR_FILE_NAME_ABSOLUTE = builder.HOME_DIR + builder.JAR_FILE; 179 180 181 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 182 // Loop all Packages in the Project 183 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 184 185 for (BuildPackage bp : builder.packageList) 186 { 187 // The user is allowed to specify (via the "BuildPackage" Constructor and one of that 188 // classes flags), to avoid / skip putting some packages into the '.jar' 189 190 if (bp.doNotJAR) continue; 191 192 // Classes considered to be "still in early development" are also not put into the jar 193 if (bp.earlyDevelopment) continue; 194 195 // Since this is just **TOO** long, use a "System.out" that just says what it's doing 196 final String note = 197 "Add to '.jar' File: " + bp.fullName + 198 (bp.hasSubPackages ? (" & " + BCYAN + "Sub-Packages" + RESET) : "") + 199 '\n'; 200 201 logAndScreen.append(note); 202 203 // This is how to PROPERLY insert files into the JAR - making-sure that their actual 204 // File-Name DOES-NOT include the Class-Path part of the directory-location, *AND* 205 // DOES include the Java-Package part of the diretory-location. 206 // 207 // For "MyProjects/src/main/My/Java/Package/SourceFile.java" 208 // The Name in the JAR for any '.class' File whose package is named: 209 // "My.Java.Package.SomeSourceFile" 210 // 211 // SHOULD INCLUDE: "My/Java/Package/SomeSourceFile.class" 212 // SHOULD NOT INCLUDE: "MyProjects/src/main/" 213 // 214 // That is all this "cpStrLen" and "classPathLocationStr-Length" and "map(substring)" 215 // stuff is actually trying to acheive. (And doing it very well, I might add) 216 // I am choosing to call this "JAR File Integrity" 217 218 final int cpStrLen = bp.classPathLocation.length(); 219 220 /* 221 System.out.println( 222 "bp.classPathLocation: [" + bp.classPathLocation + ']' + '\n' + 223 "bp.cpl.length(): " + bp.classPathLocation.length() 224 ); 225 */ 226 227 // Unlike the TAR-Files, when creating the JAR-File, the actual files being inserted 228 // have to be NAMED, EXPLICITLY. As a result, class FileNode needs to be used. 229 // Also note that because of this reason, the jar-commands will not be printed to the 230 // user's terminal (because they grow very long and unreadable / ugly). 231 232 final Stream.Builder<String> b = FileNode 233 .createRoot(bp.pkgRootDirectory) 234 .loadTree( 235 bp.hasSubPackages ? -1 : 0, 236 JAR_FILENAME_FILTER, 237 bp.hasSubPackages ? JAR_SUB_PACKAGES_DIR_FILTER : null 238 ) 239 .flattenJustFiles(RTC.FULLPATH_STREAM_BUILDER(null)); 240 241 242 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 243 // Any & All User-Provided / User-Specified "Helper Package" Sub-Directories 244 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 245 // 246 // NOTE: the "BuildPackage.helperPackages" is just a User-Provided Configuration that 247 // allows the user to add some brief sub-packages / sub-directories that remain 248 // undocumented (and, therefore, invisible) - but are still imported into the JAR 249 // 250 // The "Helper Packages" are very few/rare. For instance package "Torello.HTML" has a 251 // very small sub-directory called "parse/"... That is a helper package! 252 // "Torello.Java" has a slightly larger one called "VariableReturnType" 253 // 254 // Stuff inside of a Helper Package wasn't important enough to include it in the 255 // JAR-API, and it is not documented (not included in Java-Doc). The classes and 256 // methods they export are, indeed, public - but they just aren't mentioned anywhere 257 // because the end-users wouldn't be able to do anything with them. 258 259 for (String helperPkgDirName : bp.helperPackages) 260 { 261 logAndScreen.append( 262 " Adding Helper-Package Files: " + 263 BCYAN + helperPkgDirName + RESET + '\n' 264 ); 265 266 FileNode 267 .createRoot(helperPkgDirName) 268 .loadTree(0, JAR_FILENAME_FILTER, null) 269 .flattenJustFiles(RTC.FULLPATH_STREAM_BUILDER(b)); 270 } 271 272 273 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 274 // The "data-files/" directory 275 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 276 // 277 // If there is a "data-files/" sub-directory of a Package-Directory, then all of it's 278 // contents also need to be added to the '.jar' 279 280 final String fDataDirName = bp.pkgRootDirectory + "data-files" + FS; 281 final File fDataDir = new File(fDataDirName); 282 283 // FIRST: Check if there is a package/data-files/ directory 284 if (fDataDir.exists() && fDataDir.isDirectory()) 285 addDataFilesDir(fDataDirName, b, logAndScreen); 286 287 288 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 289 // Now Convert the Stream.Builder<String> into a String[] array 290 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 291 // 292 // In order to actually utilize the Class-Path / cpStrLen-Substring stuff, the building 293 // of these '.class' File String[]-Arrays is done using the ? : syntax thingy... 294 295 final String[] jarFilesArr = (cpStrLen == 0) 296 ? b.build().toArray(String[]::new) 297 : b.build().map(fileName -> fileName.substring(cpStrLen)).toArray(String[]::new); 298 299 300 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 301 // Run the '.jar' Command 302 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 303 // 304 // Now just build the Operating-System 'jar' Command. 305 // All that needs to happen is to concatenate the 2 command switches and the list of 306 // files from the jar-files String[]-Array that was just created previously. 307 308 String[] JAR_COMMAND = new String[2 + jarFilesArr.length]; 309 310 JAR_COMMAND[0] = firstIteration ? "-cvf" : "-uvf"; 311 JAR_COMMAND[1] = JAR_FILE_NAME_ABSOLUTE; 312 System.arraycopy(jarFilesArr, 0, JAR_COMMAND, 2, jarFilesArr.length); 313 314 if (cpStrLen > 0) 315 { 316 // REMEMBER: shell.osExtras is re-assigned null after each and every invocation 317 // shell.printAndRun(). This resetting to null is done INSIDE class 318 // OSCommands. 319 // 320 // Note that, here, we are currently inside of a for-loop, and this instruction 321 // will be executed each time the for-loop iterates - and, of course, when 322 // 'cpStrLen' is greater than zero (meaning that the current 'BuildPackage' 323 // being iterated by the loop isn't in the CWD from whence the Builder class was 324 // executed, but rather some sub-directory, which is the whole reason an 'osExtras' 325 // instance is necessary) 326 327 osExtras.currentWorkingDirectory = new File(bp.classPathLocation); 328 shell.osExtras = osExtras; 329 } 330 331 // Run the Command using class Shell. "CHECK" will actually halt the entire JRE using 332 // System.exit if there were any errors (and print a thoughtfully worded error-message) 333 334 CHECK(shell.COMMAND("jar", JAR_COMMAND)); 335 336 // This prints a total of how many files were just inserted into the jar 337 printTotals(jarFilesArr, logAndScreen); 338 339 logOnly.append(Printing.TAR_JAR_DIVIDER); 340 341 firstIteration = false; 342 } 343 344 // This chunk-o-stuff was relocated to its own dedicated method. The method simply 345 // iterates the "jarIncludes" ReadOnlyList, and adds each directory listed in the list 346 // directly into the Jar-File. A "JarInclude" is stuff like the "META-INF" - but it may be 347 // essentially anything else that needs to be placed into the '.jar' 348 // 349 // The user controls / configures this idea by instantiating some "JarInclude's", and 350 // putting into the Builder's Configuration-Constructor class "Config" 351 352 if ((builder.jarIncludes != null) && (builder.jarIncludes.size() > 0)) 353 354 addJarExtras( 355 builder.jarIncludes, JAR_FILE_NAME_ABSOLUTE, 356 shell, osExtras, logAndScreen, logOnly 357 ); 358 } 359 360 361 // ******************************************************************************************** 362 // ******************************************************************************************** 363 // HELPERS FOR: "Build JAR-File" 364 // ******************************************************************************************** 365 // ******************************************************************************************** 366 367 368 private static final FilenameFilter DEFAULT_DATA_DIR_FILTER = (File dir, String name) -> 369 StrCmpr.endsWithNAND(name, ".java", ".class"); 370 371 // Each 'BuildPackage' that is included by the 'Builder' may contain a `data-files` directory. 372 // When there is a `data-files` directory, each file in that directory ALSO NEEDS TO BE ADDED 373 // into the JAR. 374 // 375 // If the user so chooses to provide a DataFilesList.txt file inside his Package's data-files/ 376 // directory, he may do so. All this file is intended to do is specify which files inside that 377 // directory actually need to be inserted into the JAR. 378 // 379 // If there is no 'DataFilesList.txt' file, then **ALL** files in the data-files/ subdirectory 380 // are inserted into the '.jar' 381 382 private static void addDataFilesDir( 383 final String fDataDirName, 384 final Stream.Builder<String> b, 385 final AppendableSafe logAndScreen 386 ) 387 throws IOException 388 { 389 logAndScreen.append 390 (" Adding '" + BCYAN + "data-files/" + RESET + "' Directory-Contents\n"); 391 392 final String fDataFilesListFileName = fDataDirName + "DataFilesList.txt"; 393 final File fDataFilesListFile = new File(fDataFilesListFileName); 394 395 // Check if there is a src-package/data-files/DataFilesList.txt file 396 // If there is a "DataFilesList.txt" file, convert that into a File-Name List. 397 398 if (fDataFilesListFile.exists() && fDataFilesListFile.isFile()) FileRW 399 .loadFileToStream(fDataFilesListFileName, false) 400 .map((String line) -> line.trim()) 401 .filter((String line) -> line.length() > 0) 402 .filter((String line) -> ! line.startsWith("#")) 403 .map((String fileName) -> fDataDirName + fileName) 404 .forEachOrdered((String dataFileName) -> b.accept(dataFileName)); 405 406 else FileNode 407 .createRoot(fDataDirName) 408 .loadTree(-1, DEFAULT_DATA_DIR_FILTER, null) 409 .flattenJustFiles(RTC.FULLPATH_STREAM_BUILDER(b)); 410 } 411 412 413 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 414 // Add the '.jar' File-Extras. (Uses the "JarInclude" User Data-Configuration Class) 415 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 416 // 417 // Some examples of "Jar-Extras" can (hopefully / probably) be seen with the Jar-Includes from 418 // Java-HTML. These are things like the "META-INF" directories, and the Data-File Directory. 419 // 420 // final FileNodeFilter FILTER = (FileNode fn) -> 421 // StrCmpr.endsWithNAND(fn.name, ".java", ".class"); 422 // return new JarInclude() 423 // .add("", "Torello/Data/", true, FILTER, TRUE_FILTER) 424 // .add("Torello/BuildJAR/IncludeFiles/", "META-INF/", true, TRUE_FILTER, TRUE_FILTER) 425 // .add("Torello/etc/External/", "META-INF/", true, TRUE_FILTER, TRUE_FILTER); 426 427 private static void addJarExtras( 428 final Iterable<JarInclude.Descriptor> includes, 429 final String JAR_FILE_NAME_ABSOLUTE, 430 final Shell shell, 431 final OSExtras osExtras, 432 final AppendableSafe logAndScreen, 433 final StringBuilder logOnly 434 ) 435 throws IOException 436 { 437 for (JarInclude.Descriptor jid : includes) 438 { 439 logAndScreen.append("Adding Jar-Include: " + jid.toString() + '\n'); 440 441 final int cpStrLen = jid.workingDirectory.length(); 442 443 final FileNode tempFN = FileNode 444 .createRoot(jid.workingDirectory + jid.subDirectory) 445 .loadTree((jid.traverseTree ? -1 : 0), null, null); 446 447 final String[] extraFiles = (cpStrLen == 0) 448 449 ? tempFN.flatten 450 (RTC.FULLPATH_ARRAY(), -1, jid.fileFilter, true, jid.dirFilter, false) 451 452 : tempFN 453 .flatten(RTC.FULLPATH_STREAM(), -1, jid.fileFilter, true, jid.dirFilter, false) 454 .map((String fileName) -> fileName.substring(cpStrLen)) 455 .toArray(String[]::new); 456 457 String[] JAR_COMMAND = new String[2 + extraFiles.length]; 458 459 JAR_COMMAND[0] = "-uvf"; 460 JAR_COMMAND[1] = JAR_FILE_NAME_ABSOLUTE; 461 System.arraycopy(extraFiles, 0, JAR_COMMAND, 2, extraFiles.length); 462 463 if (cpStrLen > 0) 464 { 465 osExtras.currentWorkingDirectory = new File(jid.workingDirectory); 466 shell.osExtras = osExtras; 467 } 468 469 CHECK(shell.COMMAND("jar", JAR_COMMAND)); 470 471 printTotals(extraFiles, logAndScreen); 472 473 logOnly.append(Printing.TAR_JAR_DIVIDER); 474 } 475 } 476 477 478 // ******************************************************************************************** 479 // ******************************************************************************************** 480 // JAR-File: MORE HELPER's. Simpler ones 481 // ******************************************************************************************** 482 // ******************************************************************************************** 483 484 485 private static void CHECK(OSResponse osr) 486 { 487 if (osr.errorOutput.length() > 0) 488 { 489 System.out.println 490 (BRED + "\nTEXT PRINTED TO STANDARD ERROR:\n" + RESET + osr.errorOutput); 491 492 Util.ERROR_EXIT("Build Tar-Jar (using Regular Shell)"); 493 } 494 } 495 496 // This allows for '.class' files, but also includes anything else in the directory - for 497 // instance ".properties" files, or other stuff that may or may not have been left there. It 498 // seems a little questionable, but I cannot think of any generalized rule for this. So, in 499 // the end, it just makes sure to leave out the '.java' file 500 // 501 // Remember: javax.json has a ".properties" file... That's really the SINGLE-ONLY REASON that 502 // this doesn't just say instead (more specifically): name.endsWith(".class") 503 504 private static final FilenameFilter JAR_FILENAME_FILTER = 505 (File file, String name) -> ! name.endsWith(".java"); 506 507 // This isn't used at all - EXCEPT when a 'BuildPackage' claims to have "Sub-packages" 508 // "SubPackages" means that the entire directory-tree rooted at bp.pkgRootDirectory (and all of 509 // its '.class' files) needs to be included in the '.jar' 510 // 511 // NOTE: This essentially talking about the Packages: JDUInternal and BuildJAR 512 // FURTHERMORE: Since 'BuildJAR' isn't put into the '.jar', this is only used for JDUInternal 513 // 514 // The last addition to this thing was to make it print out the directories that match so that 515 // the build script tells the user all of the sub-packages. It looks nice, however, whereas 516 // this used to be a 'FileFilter' **FIELD**, it is now a **METHOD** that returns a 'FileFilter' 517 518 private static final FileFilter getSubPackageDirFilter(AppendableSafe logAndScreen) 519 { 520 // This is creating a Lambda/Predicate that **ALSO** prints out the directories that match 521 return (File file) -> 522 { 523 final String path = file.getPath(); 524 525 final boolean ret = ! StrCmpr.containsOR 526 (path, "upgrade-files", "doc-files", "package-source"); 527 528 if (ret) logAndScreen.append 529 (" Adding Sub-Package Files from: " + BCYAN + path + RESET + '\n'); 530 531 return ret; 532 }; 533 } 534 535 // Just prints out a count of how many files (and the file types) that were just inserted 536 // on the most recent loop-iteration, into the JAR. 537 538 private static void printTotals(String[] files, Appendable a) throws IOException 539 { 540 TreeMap<String, Integer> tm = new TreeMap<>(); 541 542 for (String fileName : files) 543 { 544 int slashPos = fileName.lastIndexOf(FS); 545 int dotPos = fileName.lastIndexOf('.'); 546 547 final String fileExt = ((dotPos == -1) || (slashPos > dotPos)) 548 ? "NOEXT" 549 : fileName.substring(dotPos); 550 551 tm.compute( 552 fileExt, 553 (String dummy, Integer count) -> (count == null) ? 1 : (count+1) 554 ); 555 } 556 557 for (String fileExt : tm.keySet()) a.append( 558 " Added " + 559 BGREEN + tm.get(fileExt) + RESET + 560 (fileExt.equals("NOEXT") 561 ? (BRED + " Other " + RESET) 562 : (" '" + BYELLOW + fileExt + RESET + "' ")) + 563 "Files.\n" 564 ); 565 566 // for (String fileName : files) System.out.print(fileName + ", "); 567 // System.out.println(); 568 569 a.append('\n'); 570 } 571}