001package Torello.Java.Build;
002
003import java.io.File;
004import java.io.IOException;
005import java.util.List;
006
007import Torello.Java.StringParse;
008import Torello.Java.FileNode;
009import Torello.Java.RTC;
010import Torello.Java.EXCC;
011import Torello.Java.FileRW;
012
013/**
014 * Can be used by Classes the need to build Data-File(s) for the {@code 'data-files/'}
015 * directory, or convert those Data-Files to Text for viewing and inspection.
016 */
017public abstract class DFBuilder
018{
019    // ********************************************************************************************
020    // ********************************************************************************************
021    // Instance Abstract Methods
022    // ********************************************************************************************
023    // ********************************************************************************************
024
025
026    /** Requests a list of Data-File Names that are constructed by this instance. */
027    public abstract Iterable<String> dataFileNames();
028
029    /**
030     * Requests that this Data-File Builder run and generate all Data-Files that it can build.
031     * 
032     * @param dataFileRootDir The target directory location for writing the file(s)
033     * 
034     * <BR /><BR /><B STYLE='color: red;'>Relative Directory Path:</B> This parameter is necessary
035     * becase an instance of {@code DFBuilder} might be invoked from just about anywhere.  Thus, it
036     * is imperative that the actual root {@code '../data-files/'} directory be passed to this 
037     * method, to avoid writing the Data-Files into the wrong place.
038     * 
039     * <BR /><BR /><I>This {@code String}-Parameter {@code 'dataFileRootDir'} must be a
040     * {@code String} that is a Relative-Path, from the CWD / Current-Working-Directory from whence
041     * the current Java-Instance was invoked,  to the {@code '../data-files/'} directory where the
042     * Data-Files are to be written</I>.
043     * 
044     * @throws IOException If there are any problems while writing the data-file.
045     */
046    public abstract void buildAll(String dataFileRootDir) throws IOException;
047
048    /**
049     * Requests that this Data-File Builder generate all Data-Files as Text-Files, so that
050     * they may be viewed and inspected.
051     * 
052     * <EMBED CLASS=external-html DATA-FILE-ID=DEF_IMPL_UOEX>
053     * 
054     * @param targetDir <EMBED CLASS=external-html DATA-FILE-ID=DFB_TARGET_DIR>
055     * @throws IOException <EMBED CLASS=external-html DATA-FILE-ID=IOEX>
056     * @throws UnsupportedOperationException    <EMBED CLASS=external-html DATA-FILE-ID=UOEX>
057     *
058     */
059    public void buildAllToText(String targetDir) throws IOException
060    { throw new UnsupportedOperationException(); }
061
062    /**
063     * Requests that this Data-File Builder convert the existing Data-Files themselves into 
064     * Text-Files, so that they may be viewed and inspected.
065     * 
066     * <EMBED CLASS=external-html DATA-FILE-ID=DEF_IMPL_UOEX>
067     * 
068     * @param targetDir <EMBED CLASS=external-html DATA-FILE-ID=DFB_TARGET_DIR>
069     * @throws IOException <EMBED CLASS=external-html DATA-FILE-ID=IOEX>
070     * @throws UnsupportedOperationException    <EMBED CLASS=external-html DATA-FILE-ID=UOEX>
071     */
072
073    public void deCompileAllToText(String targetDir) throws IOException
074    { throw new UnsupportedOperationException(); }
075
076
077    // ********************************************************************************************
078    // ********************************************************************************************
079    // Utility for building all Data-Files in all Packages that have a "../data-files/" directory
080    // ********************************************************************************************
081    // ********************************************************************************************
082
083
084    /**
085     * Runs / Executes the method {@link buildAll(String)} on all Class-File(s) found insdie the
086     * {@code 'data-files/'} directory for a given package.  If the package provided to parameter
087     * {@code 'pkg'} does not have a {@code 'data-files/'} directory, then this method exists 
088     * gracefully
089     * 
090     * @param pkg Any one of the Configured Project Packages.  The relevant fields used by this 
091     * method are {@link BuildPackage#classPathLocation} and {@link BuildPackage#pkgRootDirectory}.
092     * These two fields are concatenated, and that directory is searched for any / all class files
093     * that implement this {@code DFBuilder} interface.
094     * 
095     * @return The number of class-files implementing the {@code DFBuilder} interface.
096     */
097    @SuppressWarnings("unchecked")
098    public static int buildAll(final BuildPackage pkg) throws IOException
099    {
100        String  dirName = pkg.classPathLocation + pkg.pkgRootDirectory;
101        File    f       = new File(dirName);
102
103        if ((! f.exists()) || (! f.isDirectory())) return 0;
104
105        String[] fNameArr = FileNode
106            .createRoot(dirName)
107            .loadTree(-1, (File dir, String fName) -> fName.endsWith(".class"), null)
108            .flattenJustFiles(RTC.FULLPATH_ARRAY());
109
110        int counter = 0;
111
112        TOP:
113        for (String classFileName : fNameArr)
114        {
115            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
116            // FileRW.readClass
117            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
118
119            // Pulls the Class-Name out of the Class-FileName:
120            //      1) Removes the ".class" extension
121            //      2) Removes the leading "data-files/..." directory string stuff from File-Name
122
123            final String className = StringParse.beforeExtension
124                (StringParse.fromLastFileSeparatorPos(classFileName));
125
126            // Runs the FileRW.readClass method & catches the exceptions it throws.
127            final Class<?> c;
128
129            try
130                { c = FileRW.readClass(classFileName, className); }
131
132            catch (Exception e)
133                { continue; }
134
135
136            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
137            // Make sure class is a DFBuilder instance
138            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
139
140            Class<?> temp = c;
141
142            while (true)
143
144                if (temp == null)                   continue TOP;
145                else if (temp == Object.class)      continue TOP;
146                else if (temp == DFBuilder.class)   break;
147                else                                temp = temp.getSuperclass();
148
149
150            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
151            // Instantiate and call instance buildAll()
152            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
153
154            final DFBuilder dfb;
155
156            try
157                { dfb = ((Class<DFBuilder>) c).getDeclaredConstructor().newInstance(); }
158
159            catch (Exception e)
160            {
161                System.out.println(
162                    "Unable to instantiate DFBuilder.\n" +
163                    "Do you have a Zero-Argument Public Constructor?\n" +
164                    EXCC.toString(e)
165                );
166
167                System.exit(1);
168                return -1; // Shut-Up Compiler
169            }
170            
171            counter++;
172            dfb.buildAll(dirName);
173        }
174
175        return counter;
176    }
177
178
179    // ********************************************************************************************
180    // ********************************************************************************************
181    // Command-Line Interface Helper Method (public-static-void-main, to be used by sub-classes)
182    // ********************************************************************************************
183    // ********************************************************************************************
184
185
186    /**
187     * This method is intended to be used by classes that inherit this abstract class.  Classes
188     * that inherit {@code DFBuilder} may add a method into their implementation, as in the hilited
189     * source-code below.  This provides a standard Java {@code 'Main'}-Method that may be invoked
190     * as a <B STYLE='color: red;'>CLI: Command Line Interface</B>.
191     * 
192     * <DIV CLASS=EXAMPLE>{@code
193     * // The class extending DFBuilder can use this method so that it can be invoked at the
194     * // command line.  Passing '1' means that only the 'buildAll' was written by the extending
195     * // Type.
196     * 
197     * public static void main(String[] argv) { new YourDataFileBuilder().cli(1, argv); }
198     * }</DIV>
199     * 
200     * @param menuOptions The value passed to this parameter must be one of the values excplicitly
201     * named in this set: <B>{@code 1, 2, 3, 4, 6}</B>.  Any value other than these 5 numbers will
202     * force this method to throw an {@code IllegalArgumentException}.
203     * 
204     * <BR /><BR />This meaning of these is clearly explained, in the table below:
205     * 
206     * <BR /><BR /><TABLE CLASS=JDBriefTable>
207     * <TR><TH>{@code 'menuOptions'}</TH><TH>Meaning</TH></TR>
208     * 
209     * <TR> <TD>'1'</TD>
210     *      <TD>The only method implemented is the {@link #buildAll(String) buildAll} method.  The
211     *          others, on invokation, will throw the {@code UnsupportedOperationException}
212     *          </TD></TR>
213     * 
214     * <TR> <TD>'3'</TD>
215     *      <TD>{@code 3 => 1 & 2}<BR />
216     *          Methods {@link #buildAll(String) buildAll} and {@link #buildAllToText(String)
217     *          buildAllToText} are implemented, and may be invoked.
218     *
219     *          <BR /><BR >Attempting to call {@link #deCompileAllToText(String)
220     *          deCompileAllToText} would cause an {@code UnsupportedOperationException} to throw.
221     *          </TD></TR>
222     * 
223     * <TR> <TD>'4'</TD>
224     *      <TD>{@code 4 => 1 & 3}<BR />
225     *          Methods {@link #buildAll(String) buildAll} and {@link #deCompileAllToText(String)
226     *          deCompileAllToText} are implemented, and may be invoked.
227     *
228     *          <BR /><BR >Attempting to call {@link #buildAllToText(String) buildAllToText} would 
229     *          cause an {@code UnsupportedOperationException} to throw.
230     *          </TD></TR>
231     *
232     * <TR> <TD>'6'</TD>
233     *      <TD>{@code 6 ==> 1, 2 & 3}<BR />
234     *          All three methods exported by this class have been implemented, and may be invoked
235     *          without worry that the {@code UnsupportedOperationException} will throw.
236     *          </TD></TR>
237     * 
238     * </TABLE>
239     * 
240     * @param argv The Command-Line Parameter {@code String[]}-Array.  This should just be the 
241     * {@code argv}-Array that was sent to the actual Java {@code 'Main'}-Method that his invoking
242     * this helper.
243     * 
244     * @return {@code TRUE} if and only if the invoked builder method terminated successfully.
245     */
246    public final boolean cli(int menuOptions, String[] argv)
247    {
248        if ((menuOptions < 1) || (menuOptions > 6) || (menuOptions == 2) || (menuOptions == 5))
249
250            throw new IllegalArgumentException(
251                "Parameter 'menuOptions' must be a value in this set: [1, 3, 4, 6]\n" + 
252                "These are the only valid values to pass here, but [" + menuOptions + "] was " +
253                    "provided, instead."
254            );
255
256        if ((argv.length != 1) && (argv.length != 2))
257        {
258            printManPage(menuOptions);
259            return false;
260        }
261
262        final boolean   twoOK   = (menuOptions == 3) || (menuOptions == 6);
263        final boolean   threeOK = (menuOptions == 4) || (menuOptions == 6);
264        final String    dirName = (argv.length == 2) ? argv[1] : "";
265
266        try
267        {
268            switch (argv[0])
269            {
270                case "1":
271                    buildAll(dirName);
272                    break;
273
274                case "2":
275                    if (twoOK)  buildAllToText(dirName);
276                    else        printManPage(menuOptions);
277                    break;
278
279                case "3":
280                    if (threeOK)    deCompileAllToText(dirName);
281                    else            printManPage(menuOptions);
282                    break;
283            }
284        }
285
286        catch (Exception e)
287        {
288            System.out.println(
289                "This DFBuilder Instance has thrown an Exception while writing a File:\n" +
290                EXCC.toString(e) + '\n'
291            );
292
293            return false;
294        }
295
296        return true;
297    }
298
299    private static void printManPage(int menuChoice)
300    {
301        System.out.println(MAN_PAGE_LINES[0]);
302
303        if ((menuChoice == 3) || (menuChoice == 6))
304            System.out.println(MAN_PAGE_LINES[1]);
305
306        if ((menuChoice == 4) || (menuChoice == 6))
307            System.out.println(MAN_PAGE_LINES[2]);
308
309        System.out.println("\n\tOptional Directory-Name may also be passed\n");
310    }
311
312    private static final String[] MAN_PAGE_LINES = 
313    {
314        "Command Line Argument:\n\n" +
315        "\t1: Build All Data-Files Produced by this class",
316
317        "\t2: Generate Data-Files as Text-Files, for review and inspection",
318
319        "\t3: Dump Data-File(s) Produced by this Class to Text-Filess"
320    };
321}