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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
package Torello.Java.Build;

import Torello.Java.*;

import Torello.Java.Additional.BiAppendable;
import Torello.Java.Additional.Counter;

import Torello.Java.ReadOnly.ReadOnlyList;
import Torello.Java.ReadOnly.ReadOnlyArrayList;
import Torello.Java.ReadOnly.ROArrayListBuilder;

import java.io.FilenameFilter;
import java.io.File;
import java.io.IOException;

import java.util.stream.Stream;
import java.util.function.Predicate;

import static Torello.Java.C.*;

/**
 * This is the first Build-Stage, and it runs the Java-Compiler - using {@code 'java'} and
 * {@link Shell Torello.Java.Shell}.  This class also relies heavily on the Java-HTML Tools
 * {@link FileNode} and {@link FileRW}.
 * 
 * <EMBED CLASS=external-html DATA-FILE-ID=STAGE_PRIVATE_NOTE>
 * <EMBED CLASS='external-html' DATA-FILE-ID=S01_JAVA_COMPILER>
 */
@Torello.JavaDoc.StaticFunctional
public class S01_JavaCompiler
{
    // Completely irrelevant, and the 'private' modifier keeps it off of JavaDoc
    private S01_JavaCompiler() { }


    // ********************************************************************************************
    // ********************************************************************************************
    // buildCommand Helper Method
    // ********************************************************************************************
    // ********************************************************************************************


    private static ReadOnlyList<String> buildCommand
        (final BuilderRecord brec, final ReadOnlyList<String> filesToCompile)
    { 
        final ROArrayListBuilder<String> roab = new ROArrayListBuilder<>();

        if (brec.JAVA_RELEASE_NUM_SWITCH > 0)
            roab.add("--release");
                roab.add("" + brec.JAVA_RELEASE_NUM_SWITCH);

        roab.add("-encoding");
            roab.add("UTF-8");

        roab.add("-processor");
            roab.add("Torello.JDUInternal.Annotations.JDUAnnotationProcessorDispatch");

        roab.add("-classpath");
            roab.add(brec.CLASS_PATH_STR);

        if (brec.USE_XLINT_SWITCH) roab.add("-Xlint:all,-processing");

        if (brec.USE_XDIAGS_SWITCH) roab.add("-Xdiags:verbose");

        if ((brec.extraSwitchesJAVAC != null) && (brec.extraSwitchesJAVAC.size() > 0))
            for (String switchStr : brec.extraSwitchesJAVAC)
                roab.add(switchStr);

        for (String fileName : filesToCompile) roab.add(fileName);

        return roab.build();
    };


    // ********************************************************************************************
    // ********************************************************************************************
    // Print the 'javac' Command
    // ********************************************************************************************
    // ********************************************************************************************


    static void printCommandText(
            final BuilderRecord         brec,
            final ReadOnlyList<String>  javacCommand,
            final Appendable            logAndScreen,
            final Appendable            logOnly,
            final ReadOnlyList<String>  filesToCompile
        )
        throws IOException
    {
        // 'javac' Command-Path
        logAndScreen.append(
            BCYAN + "Running Java Compiler: Command Switches\n" + RESET +
            BGREEN + "INVOKING: " + RESET +
            BYELLOW + brec.JAVAC_BIN + RESET + "\n\n"
        );


        // All of the Command-Line Arguments which *ARE NOT* '.java' files SHOULD BE PRINTED to
        // 'javac'.  If a few of the '.java' Files are also printed, it isn't that big of a deal...
        //
        // The "--release" switch may or may not have been used.

        final int NUM_TWO_ARGUMENT_JAVAC_ARGS =
            4 - ((brec.JAVA_RELEASE_NUM_SWITCH <= 0) ? 1 : 0);

        final int NUM_ONE_ARGUMENT_JAVAC_ARGS =
            (brec.USE_XLINT_SWITCH ? 1 : 0) + (brec.USE_XDIAGS_SWITCH ? 1 : 0);

        // First the two-argument command line arguments are printed
        for (int i=0; i < NUM_TWO_ARGUMENT_JAVAC_ARGS; i++) logAndScreen.append
            ("    " + javacCommand.get(2 * i) + "  " + javacCommand.get(2 * i + 1) + '\n');

        // Make sure to skip over the arguments that have already been pritned
        final int SPOS = 2 * NUM_TWO_ARGUMENT_JAVAC_ARGS;

        // Print the single-argument command-line arguments
        for (int i=SPOS; i < (SPOS + NUM_ONE_ARGUMENT_JAVAC_ARGS); i++)
            logAndScreen.append("    " + javacCommand.get(i) + '\n');


        // Java-HTML doens't use this yet (as of January 2024), but if "extra" / User-Added `javac`
        // Command-Line Arguments have been configured into the "BuilderRecord" instance, then (at
        // this point in the code), those switch-arguments will have already been added/inserted
        // into the Command.  Make sure to print them out, as below:

        if ((brec.extraSwitchesJAVAC != null) && (brec.extraSwitchesJAVAC.size() > 0))
            for (String switchStr : brec.extraSwitchesJAVAC)
                logAndScreen.append(switchStr + '\n');

        // The last thing to print is this stuff...
        /* Screen Only */ System.out.println(
            "\n    [Source-Files List Omitted, Total Number of Files to Compile: " +
            BRED + filesToCompile.size() + RESET + "]\n"
        );

        for (String fileName : filesToCompile) logOnly.append("    " + fileName + '\n');
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Compile
    // ********************************************************************************************
    // ********************************************************************************************


    private static final String FS = File.separator;

    public static void compile(final BuilderRecord brec) throws IOException
    {
        // Initialize the logs, and get ready to start-up
        Printing.startStep(1);

        StringBuilder   logOnly         = new StringBuilder();
        Appendable      logAndScreen    = new BiAppendable(logOnly, System.out);

        logOnly.append("\nPackages Included in this Builder:\n\n");
        for (BuildPackage bp : brec.packageList) logOnly.append(bp.fullName + '\n');

        // List all Packages to be Compiled
        ReadOnlyList<BuildPackage> packagesToCompile = Packages.packagesToCompile(brec);

        logOnly.append("\nPackages Considered for Compilation:\n\n");
        for (BuildPackage bp : brec.packageList) logOnly.append(bp.fullName + '\n');


        // The user has the option of having all '.class' files removed from all packages which
        // are currently being re-compiled.

        if (brec.cli.aor.WIPE_CLASS_FILES_FIRST)
            deleteClassFilesFirst(packagesToCompile, logAndScreen);


        // Convert the List of Packages to a list of '.java'-Files.
        //
        // NOTE: This method invokes the Printing-Method, and actually prints out a count of the
        //       number of '.java' Files in each package that is going to be compiled.  It sort of
        //       **HAS TO** be done inside this method, because otherwise a "Count Array" would
        //       have to be returned from this method, making this much uglier.
        //
        // SEE: 'logAndScreen' is passed as ap parameter to this method.

        ReadOnlyList<String> filesToCompile = Files.filesToCompile
            (packagesToCompile, logAndScreen);

        // Build the javac Command
        ReadOnlyList<String> javacCommand = buildCommand(brec, filesToCompile);

        // Print this to the log and to terminal
        printCommandText(brec, javacCommand, logAndScreen, logOnly, filesToCompile);

        // Execute 'javac'
        OSResponse osr = new Shell(logOnly)
            .COMMAND(brec.JAVAC_BIN, javacCommand.toArray(new String[0]));


        // If there were errors, print them out and exit
        //
        // NOTE: This absolutely sucks, but in Java-17, the "Error Output" isn't actually published
        //       to the OS Standard-Error!  It all appears to be going to Standard-Out.

        if (osr.errorOutput.length() > 0)
        {
            System.err.println
                (BRED + "\nTEXT PRINTED TO STANDARD ERROR:\n" + RESET + osr.errorOutput);

            Util.ERROR_EXIT("javac"); // Calls System.exit
        }


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Move '../package-source/' CLASS-FILES to their parent/proper location
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        int max = 0;

        for (BuildPackage pkg : packagesToCompile)
            if (pkg.hasPackageSourceDir && (pkg.pkgRootDirectory.length() > max))
                max = pkg.pkgRootDirectory.length();

        // max += "package-source/".length();
        max += 15;

        boolean printed = false;

        for (final BuildPackage pkg : packagesToCompile) if (pkg.hasPackageSourceDir)
        {
            if (! printed)
            {
                logAndScreen.append(BCYAN + "\nRelocating Class Files:\n\n" + RESET);
                printed = true;
            }

            final String dir = pkg.pkgRootDirectory;

            int numClassFiles = movePackageSourceClassFiles(dir, logOnly);

            logAndScreen.append(
                "    Moved " + BRED + StringParse.zeroPad(numClassFiles) + RESET +
                " *.class File(s) From: " +
                BYELLOW + StringParse.rightSpacePad(dir + "package-source" + FS, max) + RESET +
                " To: " + BYELLOW + dir + RESET + '\n'
            );
        }


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Write the log data to the log files
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // 
        // OLD CODE: IS_FULL_COMPILE = (brec.cli.userSpecifiedPackages == null)
        // 
        // brec: BuilderRecord - Top-of-Tree Data-Record for executing a build, Contains everything
        // cli:  (Command-Line-Interface) - All info gathered from "String[] argv"
        // sor:  SelectedOptionsRecord - Information related to the Main-Menu Options

        if (brec.cli.sor.userSpecifiedPackages == null)
            brec.logs.write_S01_LOGS(logOnly.toString(), osr.standardOutput, osr.errorOutput);
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Util
    // ********************************************************************************************
    // ********************************************************************************************


    private static void deleteClassFilesFirst(
            final ReadOnlyList<BuildPackage>    packagesToCompile,
            final Appendable                    logAndScreen
        )
        throws IOException
    {
        // Convert the List of Packages to a list of '.class'-Files
        // (These files are to be deleted).
        //
        // NOTE: This method invokes the Printing-Method, and actually prints out a count of the
        //       number of '.class' Files in each package.
        //
        // SEE: 'logAndScreen' is passed as ap parameter to this method.

        ReadOnlyList<String> filesToCompile = Files.classFilesToDeleteBeforeCompilation
            (packagesToCompile, logAndScreen);

        logAndScreen.append(
            BCYAN + "Deleting " + RESET + BYELLOW + "'.class'" + RESET + 
            BCYAN + " Files, before javac\n\n" + RESET
        );

        FileRW.deleteFiles(filesToCompile.toArray(String[]:: new));
    }

    private static int movePackageSourceClassFiles(final String dir, final Appendable a)
    {
        Counter c = new Counter();

        FileNode
            .createRoot(dir + "package-source" + FS)
            .loadTree(-1, FileNode.CLASS_FILES, null)
            .flattenJustFiles(RTC.FULLPATH_VECTOR())
            .forEach((String srcFileName) ->
            {
                c.addOne();

                try
                {
                    FileRW.moveFile(srcFileName, dir, false);
                    a.append("Moved " + srcFileName + " to " + dir + '\n');
                }

                catch (IOException ioe)
                { 
                    System.err.println(
                        EXCC.toString(ioe) + '\n' +
                        "Fatal Error, Exiting Build\n"
                    );

                    System.exit(1);
                }
            });

        return c.get();
    }
}