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
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=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
        (Builder builder, ReadOnlyList<String> filesToCompile)
    { 
        final ROArrayListBuilder<String> roab = new ROArrayListBuilder<>();

        if (builder.JAVA_RELEASE_NUM_SWITCH > 0)
            roab.add("--release");
                roab.add("" + builder.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(builder.CLASS_PATH_STR);

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

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

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

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

        return roab.build();
    };


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


    static void printCommandText(
            Builder                 builder,
            ReadOnlyList<String>    javacCommand,
            Appendable              logAndScreen,
            Appendable              logOnly,
            ReadOnlyList<String>    filesToCompile
        )
        throws IOException
    {
        // 'javac' Command-Path
        logAndScreen.append(
            BCYAN + "Running Java Compiler: Command Switches\n" + RESET +
            BGREEN + "INVOKING: " + RESET +
            BYELLOW + builder.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 - ((builder.JAVA_RELEASE_NUM_SWITCH <= 0) ? 1 : 0);

        final int NUM_ONE_ARGUMENT_JAVAC_ARGS =
            (builder.USE_XLINT_SWITCH ? 1 : 0) + (builder.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 "Builder" 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 ((builder.extraSwitchesJAVAC != null) && (builder.extraSwitchesJAVAC.size() > 0))
            for (String switchStr : builder.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(Builder builder) 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 : builder.packageList) logOnly.append(bp.fullName + '\n');

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

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

        // Convert the List of Packages to a list of files
        ReadOnlyList<String> filesToCompile = Files.filesToCompile
            (packagesToCompile, logAndScreen);

        // Build the javadoc Command, Print the Command-Line Arguments to Screen
        ReadOnlyList<String> javacCommand = buildCommand(builder, filesToCompile);

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

        // Execute 'javac'
        OSResponse osr = new Shell(logOnly)
            .COMMAND(builder.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 = (builder.cli.userSpecifiedPackages == null)
        if (builder.cli.userSpecifiedPackages == null)
            builder.logs.write_S01_LOGS(logOnly.toString(), osr.standardOutput, osr.errorOutput);
    }

    private static int movePackageSourceClassFiles(final String dir, 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();
    }
}