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=S01_JAVA_COMPILER>
027 */
028@Torello.JavaDoc.StaticFunctional
029public class S01_JavaCompiler
030{
031    // Completely irrelevant, and the 'private' modifier keeps it off of JavaDoc
032    private S01_JavaCompiler() { }
033
034
035    // ********************************************************************************************
036    // ********************************************************************************************
037    // buildCommand Helper Method
038    // ********************************************************************************************
039    // ********************************************************************************************
040
041
042    private static ReadOnlyList<String> buildCommand
043        (Builder builder, ReadOnlyList<String> filesToCompile)
044    { 
045        final ROArrayListBuilder<String> roab = new ROArrayListBuilder<>();
046
047        if (builder.JAVA_RELEASE_NUM_SWITCH > 0)
048            roab.add("--release");
049                roab.add("" + builder.JAVA_RELEASE_NUM_SWITCH);
050
051        roab.add("-encoding");
052            roab.add("UTF-8");
053
054        roab.add("-processor");
055            roab.add("Torello.JDUInternal.Annotations.JDUAnnotationProcessorDispatch");
056
057        roab.add("-classpath");
058            roab.add(builder.CLASS_PATH_STR);
059
060        if (builder.USE_XLINT_SWITCH) roab.add("-Xlint:all,-processing");
061
062        if (builder.USE_XDIAGS_SWITCH) roab.add("-Xdiags:verbose");
063
064        if ((builder.extraSwitchesJAVAC != null) && (builder.extraSwitchesJAVAC.size() > 0))
065            for (String switchStr : builder.extraSwitchesJAVAC)
066                roab.add(switchStr);
067
068        for (String fileName : filesToCompile) roab.add(fileName);
069
070        return roab.build();
071    };
072
073
074    // ********************************************************************************************
075    // ********************************************************************************************
076    // Print the 'javac' Command
077    // ********************************************************************************************
078    // ********************************************************************************************
079
080
081    static void printCommandText(
082            Builder                 builder,
083            ReadOnlyList<String>    javacCommand,
084            Appendable              logAndScreen,
085            Appendable              logOnly,
086            ReadOnlyList<String>    filesToCompile
087        )
088        throws IOException
089    {
090        // 'javac' Command-Path
091        logAndScreen.append(
092            BCYAN + "Running Java Compiler: Command Switches\n" + RESET +
093            BGREEN + "INVOKING: " + RESET +
094            BYELLOW + builder.JAVAC_BIN + RESET + "\n\n"
095        );
096
097        // All of the Command-Line Arguments which *ARE NOT* '.java' files SHOULD BE PRINTED to
098        // 'javac'.  If a few of the '.java' Files are also printed, it isn't that big of a deal...
099        //
100        // The "--release" switch may or may not have been used.
101
102        final int NUM_TWO_ARGUMENT_JAVAC_ARGS =
103            4 - ((builder.JAVA_RELEASE_NUM_SWITCH <= 0) ? 1 : 0);
104
105        final int NUM_ONE_ARGUMENT_JAVAC_ARGS =
106            (builder.USE_XLINT_SWITCH ? 1 : 0) + (builder.USE_XDIAGS_SWITCH ? 1 : 0);
107
108        // First the two-argument command line arguments are printed
109        for (int i=0; i < NUM_TWO_ARGUMENT_JAVAC_ARGS; i++) logAndScreen.append
110            ("    " + javacCommand.get(2 * i) + "  " + javacCommand.get(2 * i + 1) + '\n');
111
112        // Make sure to skip over the arguments that have already been pritned
113        final int SPOS = 2 * NUM_TWO_ARGUMENT_JAVAC_ARGS;
114
115        // Print the single-argument command-line arguments
116        for (int i=SPOS; i < (SPOS + NUM_ONE_ARGUMENT_JAVAC_ARGS); i++)
117            logAndScreen.append("    " + javacCommand.get(i) + '\n');
118
119        // Java-HTML doens't use this yet (as of January 2024), but if "extra" / User-Added `javac`
120        // Command-Line Arguments have been configured into the "Builder" instance, then (at this
121        // point in the code), those switch-arguments will have already been added/inserted into
122        // the Command.  Make sure to print them out, as below:
123
124        if ((builder.extraSwitchesJAVAC != null) && (builder.extraSwitchesJAVAC.size() > 0))
125            for (String switchStr : builder.extraSwitchesJAVAC)
126                logAndScreen.append(switchStr + '\n');
127
128        // The last thing to print is this stuff...
129        /* Screen Only */ System.out.println(
130            "\n    [Source-Files List Omitted, Total Number of Files to Compile: " +
131            BRED + filesToCompile.size() + RESET + "]\n"
132        );
133
134        for (String fileName : filesToCompile) logOnly.append("    " + fileName + '\n');
135    }
136
137
138    // ********************************************************************************************
139    // ********************************************************************************************
140    // Compile
141    // ********************************************************************************************
142    // ********************************************************************************************
143
144
145    private static final String FS = File.separator;
146
147    public static void compile(Builder builder) throws IOException
148    {
149        // Initialize the logs, and get ready to start-up
150        Printing.startStep(1);
151
152        StringBuilder   logOnly         = new StringBuilder();
153        Appendable      logAndScreen    = new BiAppendable(logOnly, System.out);
154
155        logOnly.append("\nPackages Included in this Builder:\n\n");
156        for (BuildPackage bp : builder.packageList) logOnly.append(bp.fullName + '\n');
157
158        // List all Packages to be Compiled
159        ReadOnlyList<BuildPackage> packagesToCompile = Packages.packagesToCompile(builder);
160
161        logOnly.append("\nPackages Considered for Compilation:\n\n");
162        for (BuildPackage bp : builder.packageList) logOnly.append(bp.fullName + '\n');
163
164        // Convert the List of Packages to a list of files
165        ReadOnlyList<String> filesToCompile = Files.filesToCompile
166            (packagesToCompile, logAndScreen);
167
168        // Build the javadoc Command, Print the Command-Line Arguments to Screen
169        ReadOnlyList<String> javacCommand = buildCommand(builder, filesToCompile);
170
171        // Print this to the log and to terminal
172        printCommandText(builder, javacCommand, logAndScreen, logOnly, filesToCompile);
173
174        // Execute 'javac'
175        OSResponse osr = new Shell(logOnly)
176            .COMMAND(builder.JAVAC_BIN, javacCommand.toArray(new String[0]));
177
178        // If there were errors, print them out and exit
179        //
180        // NOTE: This absolutely sucks, but in Java-17, the "Error Output" isn't actually published
181        //       to the OS Standard-Error!  It all appears to be going to Standard-Out.
182
183        if (osr.errorOutput.length() > 0)
184        {
185            System.err.println
186                (BRED + "\nTEXT PRINTED TO STANDARD ERROR:\n" + RESET + osr.errorOutput);
187
188            Util.ERROR_EXIT("javac"); // Calls System.exit
189        }
190
191
192        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
193        // Move '../package-source/' CLASS-FILES to their parent/proper location
194        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
195
196        int max = 0;
197
198        for (BuildPackage pkg : packagesToCompile)
199            if (pkg.hasPackageSourceDir && (pkg.pkgRootDirectory.length() > max))
200                max = pkg.pkgRootDirectory.length();
201
202        // max += "package-source/".length();
203        max += 15;
204
205        boolean printed = false;
206
207        for (final BuildPackage pkg : packagesToCompile) if (pkg.hasPackageSourceDir)
208        {
209            if (! printed)
210            {
211                logAndScreen.append(BCYAN + "\nRelocating Class Files:\n\n" + RESET);
212                printed = true;
213            }
214
215            final String dir = pkg.pkgRootDirectory;
216
217            int numClassFiles = movePackageSourceClassFiles(dir, logOnly);
218
219            logAndScreen.append(
220                "    Moved " + BRED + StringParse.zeroPad(numClassFiles) + RESET +
221                " *.class File(s) From: " +
222                BYELLOW + StringParse.rightSpacePad(dir + "package-source" + FS, max) + RESET +
223                " To: " + BYELLOW + dir + RESET + '\n'
224            );
225        }
226
227
228        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
229        // Write the log data to the log files
230        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
231
232        // OLD CODE: IS_FULL_COMPILE = (builder.cli.userSpecifiedPackages == null)
233        if (builder.cli.userSpecifiedPackages == null)
234            builder.logs.write_S01_LOGS(logOnly.toString(), osr.standardOutput, osr.errorOutput);
235    }
236
237    private static int movePackageSourceClassFiles(final String dir, Appendable a)
238    {
239        Counter c = new Counter();
240
241        FileNode
242            .createRoot(dir + "package-source" + FS)
243            .loadTree(-1, FileNode.CLASS_FILES, null)
244            .flattenJustFiles(RTC.FULLPATH_VECTOR())
245            .forEach((String srcFileName) ->
246            {
247                c.addOne();
248
249                try
250                {
251                    FileRW.moveFile(srcFileName, dir, false);
252                    a.append("Moved " + srcFileName + " to " + dir + '\n');
253                }
254
255                catch (IOException ioe)
256                { 
257                    System.err.println(
258                        EXCC.toString(ioe) + '\n' +
259                        "Fatal Error, Exiting Build\n"
260                    );
261
262                    System.exit(1);
263                }
264            });
265
266        return c.get();
267    }
268}