001package Torello.Java.Build;
002
003import Torello.Java.*;
004
005import Torello.Java.Additional.BiAppendable;
006import Torello.Java.Additional.Counter;
007
008import Torello.Java.ReadOnly.ReadOnlyList;
009import Torello.Java.ReadOnly.ReadOnlyArrayList;
010import Torello.Java.ReadOnly.ROArrayListBuilder;
011
012import java.io.FilenameFilter;
013import java.io.File;
014import java.io.IOException;
015
016import java.util.stream.Stream;
017import java.util.function.Predicate;
018
019import static Torello.Java.C.*;
020
021/**
022 * This is the first Build-Stage, and it runs the Java-Compiler - using {@code 'java'} and
023 * {@link Shell Torello.Java.Shell}.  This class also relies heavily on the Java-HTML Tools
024 * {@link FileNode} and {@link FileRW}.
025 * 
026 * <EMBED CLASS=external-html DATA-FILE-ID=STAGE_PRIVATE_NOTE>
027 * <EMBED CLASS='external-html' DATA-FILE-ID=S01_JAVA_COMPILER>
028 */
029@Torello.JavaDoc.StaticFunctional
030public class S01_JavaCompiler
031{
032    // Completely irrelevant, and the 'private' modifier keeps it off of JavaDoc
033    private S01_JavaCompiler() { }
034
035
036    // ********************************************************************************************
037    // ********************************************************************************************
038    // buildCommand Helper Method
039    // ********************************************************************************************
040    // ********************************************************************************************
041
042
043    private static ReadOnlyList<String> buildCommand
044        (final BuilderRecord brec, final ReadOnlyList<String> filesToCompile)
045    { 
046        final ROArrayListBuilder<String> roab = new ROArrayListBuilder<>();
047
048        if (brec.JAVA_RELEASE_NUM_SWITCH > 0)
049            roab.add("--release");
050                roab.add("" + brec.JAVA_RELEASE_NUM_SWITCH);
051
052        roab.add("-encoding");
053            roab.add("UTF-8");
054
055        roab.add("-processor");
056            roab.add("Torello.JDUInternal.Annotations.JDUAnnotationProcessorDispatch");
057
058        roab.add("-classpath");
059            roab.add(brec.CLASS_PATH_STR);
060
061        if (brec.USE_XLINT_SWITCH) roab.add("-Xlint:all,-processing");
062
063        if (brec.USE_XDIAGS_SWITCH) roab.add("-Xdiags:verbose");
064
065        if ((brec.extraSwitchesJAVAC != null) && (brec.extraSwitchesJAVAC.size() > 0))
066            for (String switchStr : brec.extraSwitchesJAVAC)
067                roab.add(switchStr);
068
069        for (String fileName : filesToCompile) roab.add(fileName);
070
071        return roab.build();
072    };
073
074
075    // ********************************************************************************************
076    // ********************************************************************************************
077    // Print the 'javac' Command
078    // ********************************************************************************************
079    // ********************************************************************************************
080
081
082    static void printCommandText(
083            final BuilderRecord         brec,
084            final ReadOnlyList<String>  javacCommand,
085            final Appendable            logAndScreen,
086            final Appendable            logOnly,
087            final ReadOnlyList<String>  filesToCompile
088        )
089        throws IOException
090    {
091        // 'javac' Command-Path
092        logAndScreen.append(
093            BCYAN + "Running Java Compiler: Command Switches\n" + RESET +
094            BGREEN + "INVOKING: " + RESET +
095            BYELLOW + brec.JAVAC_BIN + RESET + "\n\n"
096        );
097
098
099        // All of the Command-Line Arguments which *ARE NOT* '.java' files SHOULD BE PRINTED to
100        // 'javac'.  If a few of the '.java' Files are also printed, it isn't that big of a deal...
101        //
102        // The "--release" switch may or may not have been used.
103
104        final int NUM_TWO_ARGUMENT_JAVAC_ARGS =
105            4 - ((brec.JAVA_RELEASE_NUM_SWITCH <= 0) ? 1 : 0);
106
107        final int NUM_ONE_ARGUMENT_JAVAC_ARGS =
108            (brec.USE_XLINT_SWITCH ? 1 : 0) + (brec.USE_XDIAGS_SWITCH ? 1 : 0);
109
110        // First the two-argument command line arguments are printed
111        for (int i=0; i < NUM_TWO_ARGUMENT_JAVAC_ARGS; i++) logAndScreen.append
112            ("    " + javacCommand.get(2 * i) + "  " + javacCommand.get(2 * i + 1) + '\n');
113
114        // Make sure to skip over the arguments that have already been pritned
115        final int SPOS = 2 * NUM_TWO_ARGUMENT_JAVAC_ARGS;
116
117        // Print the single-argument command-line arguments
118        for (int i=SPOS; i < (SPOS + NUM_ONE_ARGUMENT_JAVAC_ARGS); i++)
119            logAndScreen.append("    " + javacCommand.get(i) + '\n');
120
121
122        // Java-HTML doens't use this yet (as of January 2024), but if "extra" / User-Added `javac`
123        // Command-Line Arguments have been configured into the "BuilderRecord" instance, then (at
124        // this point in the code), those switch-arguments will have already been added/inserted
125        // into the Command.  Make sure to print them out, as below:
126
127        if ((brec.extraSwitchesJAVAC != null) && (brec.extraSwitchesJAVAC.size() > 0))
128            for (String switchStr : brec.extraSwitchesJAVAC)
129                logAndScreen.append(switchStr + '\n');
130
131        // The last thing to print is this stuff...
132        /* Screen Only */ System.out.println(
133            "\n    [Source-Files List Omitted, Total Number of Files to Compile: " +
134            BRED + filesToCompile.size() + RESET + "]\n"
135        );
136
137        for (String fileName : filesToCompile) logOnly.append("    " + fileName + '\n');
138    }
139
140
141    // ********************************************************************************************
142    // ********************************************************************************************
143    // Compile
144    // ********************************************************************************************
145    // ********************************************************************************************
146
147
148    private static final String FS = File.separator;
149
150    public static void compile(final BuilderRecord brec) throws IOException
151    {
152        // Initialize the logs, and get ready to start-up
153        Printing.startStep(1);
154
155        StringBuilder   logOnly         = new StringBuilder();
156        Appendable      logAndScreen    = new BiAppendable(logOnly, System.out);
157
158        logOnly.append("\nPackages Included in this Builder:\n\n");
159        for (BuildPackage bp : brec.packageList) logOnly.append(bp.fullName + '\n');
160
161        // List all Packages to be Compiled
162        ReadOnlyList<BuildPackage> packagesToCompile = Packages.packagesToCompile(brec);
163
164        logOnly.append("\nPackages Considered for Compilation:\n\n");
165        for (BuildPackage bp : brec.packageList) logOnly.append(bp.fullName + '\n');
166
167
168        // The user has the option of having all '.class' files removed from all packages which
169        // are currently being re-compiled.
170
171        if (brec.cli.aor.WIPE_CLASS_FILES_FIRST)
172            deleteClassFilesFirst(packagesToCompile, logAndScreen);
173
174
175        // Convert the List of Packages to a list of '.java'-Files.
176        //
177        // NOTE: This method invokes the Printing-Method, and actually prints out a count of the
178        //       number of '.java' Files in each package that is going to be compiled.  It sort of
179        //       **HAS TO** be done inside this method, because otherwise a "Count Array" would
180        //       have to be returned from this method, making this much uglier.
181        //
182        // SEE: 'logAndScreen' is passed as ap parameter to this method.
183
184        ReadOnlyList<String> filesToCompile = Files.filesToCompile
185            (packagesToCompile, logAndScreen);
186
187        // Build the javac Command
188        ReadOnlyList<String> javacCommand = buildCommand(brec, filesToCompile);
189
190        // Print this to the log and to terminal
191        printCommandText(brec, javacCommand, logAndScreen, logOnly, filesToCompile);
192
193        // Execute 'javac'
194        OSResponse osr = new Shell(logOnly)
195            .COMMAND(brec.JAVAC_BIN, javacCommand.toArray(new String[0]));
196
197
198        // If there were errors, print them out and exit
199        //
200        // NOTE: This absolutely sucks, but in Java-17, the "Error Output" isn't actually published
201        //       to the OS Standard-Error!  It all appears to be going to Standard-Out.
202
203        if (osr.errorOutput.length() > 0)
204        {
205            System.err.println
206                (BRED + "\nTEXT PRINTED TO STANDARD ERROR:\n" + RESET + osr.errorOutput);
207
208            Util.ERROR_EXIT("javac"); // Calls System.exit
209        }
210
211
212        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
213        // Move '../package-source/' CLASS-FILES to their parent/proper location
214        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
215
216        int max = 0;
217
218        for (BuildPackage pkg : packagesToCompile)
219            if (pkg.hasPackageSourceDir && (pkg.pkgRootDirectory.length() > max))
220                max = pkg.pkgRootDirectory.length();
221
222        // max += "package-source/".length();
223        max += 15;
224
225        boolean printed = false;
226
227        for (final BuildPackage pkg : packagesToCompile) if (pkg.hasPackageSourceDir)
228        {
229            if (! printed)
230            {
231                logAndScreen.append(BCYAN + "\nRelocating Class Files:\n\n" + RESET);
232                printed = true;
233            }
234
235            final String dir = pkg.pkgRootDirectory;
236
237            int numClassFiles = movePackageSourceClassFiles(dir, logOnly);
238
239            logAndScreen.append(
240                "    Moved " + BRED + StringParse.zeroPad(numClassFiles) + RESET +
241                " *.class File(s) From: " +
242                BYELLOW + StringParse.rightSpacePad(dir + "package-source" + FS, max) + RESET +
243                " To: " + BYELLOW + dir + RESET + '\n'
244            );
245        }
246
247
248        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
249        // Write the log data to the log files
250        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
251        // 
252        // OLD CODE: IS_FULL_COMPILE = (brec.cli.userSpecifiedPackages == null)
253        // 
254        // brec: BuilderRecord - Top-of-Tree Data-Record for executing a build, Contains everything
255        // cli:  (Command-Line-Interface) - All info gathered from "String[] argv"
256        // sor:  SelectedOptionsRecord - Information related to the Main-Menu Options
257
258        if (brec.cli.sor.userSpecifiedPackages == null)
259            brec.logs.write_S01_LOGS(logOnly.toString(), osr.standardOutput, osr.errorOutput);
260    }
261
262
263    // ********************************************************************************************
264    // ********************************************************************************************
265    // Util
266    // ********************************************************************************************
267    // ********************************************************************************************
268
269
270    private static void deleteClassFilesFirst(
271            final ReadOnlyList<BuildPackage>    packagesToCompile,
272            final Appendable                    logAndScreen
273        )
274        throws IOException
275    {
276        // Convert the List of Packages to a list of '.class'-Files
277        // (These files are to be deleted).
278        //
279        // NOTE: This method invokes the Printing-Method, and actually prints out a count of the
280        //       number of '.class' Files in each package.
281        //
282        // SEE: 'logAndScreen' is passed as ap parameter to this method.
283
284        ReadOnlyList<String> filesToCompile = Files.classFilesToDeleteBeforeCompilation
285            (packagesToCompile, logAndScreen);
286
287        logAndScreen.append(
288            BCYAN + "Deleting " + RESET + BYELLOW + "'.class'" + RESET + 
289            BCYAN + " Files, before javac\n\n" + RESET
290        );
291
292        FileRW.deleteFiles(filesToCompile.toArray(String[]:: new));
293    }
294
295    private static int movePackageSourceClassFiles(final String dir, final Appendable a)
296    {
297        Counter c = new Counter();
298
299        FileNode
300            .createRoot(dir + "package-source" + FS)
301            .loadTree(-1, FileNode.CLASS_FILES, null)
302            .flattenJustFiles(RTC.FULLPATH_VECTOR())
303            .forEach((String srcFileName) ->
304            {
305                c.addOne();
306
307                try
308                {
309                    FileRW.moveFile(srcFileName, dir, false);
310                    a.append("Moved " + srcFileName + " to " + dir + '\n');
311                }
312
313                catch (IOException ioe)
314                { 
315                    System.err.println(
316                        EXCC.toString(ioe) + '\n' +
317                        "Fatal Error, Exiting Build\n"
318                    );
319
320                    System.exit(1);
321                }
322            });
323
324        return c.get();
325    }
326}