001package Torello.HTML.Tools.JavaDoc;
002
003import Torello.HTML.*;
004import Torello.HTML.NodeSearch.*;
005import Torello.Java.*;
006
007import Torello.Java.Shell.C;
008
009import java.util.*;
010import java.util.stream.*;
011import java.io.*;
012
013import java.util.function.ToIntFunction;
014
015/**
016 * <CODE>RearrangeSummaries - Documentation.</CODE><BR /><BR />
017 * <EMBED CLASS="external-html" DATA-FILE-ID="REARSUMM">
018 */
019@StaticFunctional
020public class RearrangeSummaries
021{
022    private RearrangeSummaries() { }
023
024    /**
025     * <CODE>SummaryItems - Documentation.</CODE><BR /><BR />
026     * <EMBED CLASS="external-html" DATA-FILE-ID="RSITEMS">
027     */
028    public static class SummaryItems
029    {
030        /**
031         * This is the list of classes, interfaces or enums that shall have at least one of 
032         * their summary sections rearranged.  The names passed to this field must contain full
033         * and complete package-names.  <I>The 'Simple Class Name' is insufficient!</I>  This array
034         * shall be parallel (and, therefore, of equal length) to the other arrays in this class.
035         */
036        public final String[] ciets;
037
038        /**
039         * This should contain the names of the sections in which the fields, constructors or
040         * methods will be divided.  Each sub-array of this two dimensional array shall contain
041         * a list for one CIET.  This array must be parallel to the other arrays in this class.
042         */
043        public final String[][] sectionNames;
044
045        /**
046         * This array should identify which of the summary sections are to be rearranged.  The
047         * Summary Sections in a JavaDoc web-page that can be re-arranged include fields, methods
048         * and constructors.
049         */
050        public final FCM[] sections;
051
052        /**
053         * This is function-pointer that you must provide to sort the methods, fields or 
054         * constructors in a summary-section.  Each of these
055         * {@code java.util.function.IntFuction's} must have an {@code int apply(T t)} method
056         * that will return the array-index of the section-name where the method, field or
057         * constructor will be placed.
058         * 
059         * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> The {@code IntFunction<T>} that are
060         * members of this list must have a type that is one of {@code Method, Field} or
061         * {@code Constructor}.
062         */
063        @SuppressWarnings("rawtypes") 
064        public final ToIntFunction[] sorters;
065
066        /** Internally used constructor for this class. */
067        public SummaryItems(
068                String[] ciets, String[][] sectionNames, FCM[] sections,
069                @SuppressWarnings("rawtypes") ToIntFunction[] sorters,
070                String rootSourceFileDirectory
071            )
072        {
073            this.ciets          = ciets;
074            this.sectionNames   = sectionNames;
075            this.sections       = sections;
076            this.sorters        = sorters;
077
078            try
079                { checkArrays(rootSourceFileDirectory); }
080            catch (Throwable t)
081            {
082                SummaryRearrangeException sre = new SummaryRearrangeException(
083                    "There was an error checking the input arrays for the Summary Rearrangement " +
084                    "that was requested.  Please see the Throwable.getCause() for details.",
085                    t
086                );
087                Upgrade.ERROR
088                    (sre, "Failed setting SummaryItems Input-Arrays (RearrangeSummaries)");
089            }
090        }
091
092        private void checkArrays(String rootSrcDir) throws IOException
093        {
094            // This whole thing just checks that these arrays have equal lengths, and throws a
095            // consistently formatted error message if they are not.  It also checks for null, and
096            // throws a NullPointerException in such cases.
097            ParallelArrayException.check
098                (ciets, "ciets", true, sectionNames, "sectionNames", true);
099
100            ParallelArrayException.check
101                (sections, "sections", true, sectionNames, "sectionNames");
102
103            ParallelArrayException.check
104                (sorters, "sorters", true, sections, "sections");
105
106            // Just does another null-pointer check.
107            for (String[] cietSectionNames : sectionNames)
108
109                for (String sectionName : cietSectionNames)
110
111                    if (sectionName == null) throw new NullPointerException(
112                        "The sectionNames[][] array passed to this method contains a null " +
113                        "reference."
114                    );
115
116            // Check that the CIET's in the ciet-array actually exist on disk.
117            //
118            // This is not really necessary, because it if the user provided a CIET that was
119            // 'misspelled' or had been 'deprecated', the Upgrader Logic would just skip that CIET
120            // since it would never encounter it in the JavaDog pages in the first place!
121            //
122            // This seems 'smarter' because it lets them know when they have erroneous stuff in
123            // their configuration files.
124
125            TOP:
126            for (String ciet : ciets)
127            {
128                String fName = rootSrcDir + ciet.replace(".", File.separator) + ".java";
129
130                if (new File(fName).exists()) continue;
131
132                StringBuilder   sb  = new StringBuilder();
133                int             pos = fName.length();
134
135                while ((pos = fName.lastIndexOf(Upgrade.FSEP)) != -1)
136                {
137                    fName = fName.substring(0, pos) + ".java";
138
139                    if (new File(fName).exists()) continue TOP;
140
141                    sb.append("      " + fName + '\n');
142                }
143
144                throw new FileNotFoundException(
145                    "One of the CIET - Classes, Interfaces or Enumerated-Types provided to " +
146                    "the RearrangeSummaries.SummaryItems Constructor points to a '.java' " +
147                    "file that could not be located on disk.\n" +
148                    "CIET: " + ciet + "\n" +
149                    "DISK: " + rootSrcDir + ciet.replace(".", File.separator) + ".java" +
150                    ((sb.length() > 0)
151                        ?
152                        "NOTE: Attempts to check if this is actually a (static) inner-class were " +
153                        "made, but they also failed.\n" +
154                        "Also Checked:\n" +
155                        sb.toString()
156                        : ""
157                    )
158                );
159            }
160        }
161    }
162
163    // retrieves the appropriate summary-pointer
164    private static DotPair summaryDP(Vector<HTMLNode> fileVec, FCM fcm)
165    {
166        switch (fcm)
167        {
168            case Field:         return Summaries.fieldSummaries(fileVec);
169            case Method:        return Summaries.methodSummaries(fileVec);
170            case Constructor:   return Summaries.constructorSummaries(fileVec);
171            default: throw new UnreachableError();
172        }
173    }
174
175    /**
176     * Runs it.
177     * 
178     * @param pr This contains all of the needed parameters for this method, encapsulated into a
179     * single record-class.  The list is somewhat lengthy, so this makes the code "look cleaner"
180     * 
181     * @param sort The sort information requested by the user.
182     */
183    public static void run(CommonParamRecord pr, SummaryItems sort)
184    {
185        // This minor detail just makes sure we don't run a sort on any Summary-Section twice.
186        // This is only for potential user-errror.  If they have provided two different sorting
187        // functions for the same Summary-Section, an exception has to be thrown (or else a real
188        // bad NullPointerException will eventually be thrown)
189        TreeSet<FCM> alreadyRan = new TreeSet<>();
190
191        // See if there is a match between the Class, Interface or Enum that was passed with one
192        // of the user-requets for Summary-Rearrangement.
193        //
194        // NOTE: There may be more than one type of sort provided for the same class!
195
196        for (int arrPos=0; arrPos < sort.ciets.length; arrPos++)
197
198            if (sort.ciets[arrPos].equals(pr.cietFullName))
199            {
200                FCM fcm = sort.sections[arrPos];
201
202                if (alreadyRan.contains(fcm)) throw new SummaryRearrangeException(
203                    "While parsing the " + fcm + " list in the Summaries Section for File:\n" +
204                    pr.jdFileName + "\nOne of the sorters provided is actually a second-sorter.  " +
205                    "The " + fcm + " Summary Section, has already been sorted.  You have " +
206                    "provided two sorts for the " + fcm + " Summary Section."
207                );
208
209                run(pr, fcm, sort.sectionNames[arrPos], sort.sorters[arrPos]);
210
211                alreadyRan.add(fcm);
212            }
213    }
214
215    @SuppressWarnings("unchecked")
216    private static void run(
217            CommonParamRecord pr, FCM fcm, String[] sectionNames,
218            @SuppressWarnings("rawtypes") ToIntFunction sorter
219        )
220    {
221        // This is used at the **VERY END** of the main while-loop.  This is the "title-row"
222        // for each of the "sections."  Since this method may be rearranging Methods, Fields or
223        // Constructors - there must be a different <TR>...</TR> depending on which it is.
224        // The 'titleRow' Vectors are private fields defined near the bottom of this class.
225        Vector<HTMLNode> titleRow = null;
226
227        switch (fcm)
228        {
229            case Field:         titleRow = titleRowMethodOrField;
230                                titleRow.setElementAt(new TextNode("Field"), FIELD_METH_NAME_POS);
231                                break;
232            case Method:        titleRow = titleRowMethodOrField;
233                                titleRow.setElementAt(new TextNode("Method"), FIELD_METH_NAME_POS);
234                                break;
235            case Constructor:   titleRow = titleRowConstructor;
236                                break;
237        }
238
239        // Build the table row iterator.  Make sure to restrict the cursor to just the summary
240        // table that is being re-arranged.
241        DotPair         summaryDP   = summaryDP(pr.fileVec, fcm);
242        HNLIInclusive   iter        = TagNodeInclusiveIterator.iter(pr.fileVec, "tr");
243
244        iter.restrictCursor(summaryDP);
245
246        // This is just an "overly complicated" way to sort the table-rows into their appropriate
247        // sections.  Once you are used to "Java Streams", then sorting things into groups always
248        // looks like this with (complex-looking) Stream.Builders.  However, this is actually very
249        // simple.  This is just a "list of lists".  Streams always make it easier since the length
250        // of each sub-array is unknown until the sort has been complicated.
251        //
252        // There is one Stream.Builder for each of the sections in the particular summary.
253        // This is why this is an 'array' with the '[]' notation.
254        //
255        // The contents that are placed into each Stream are an HTML Table Row, as a Vector of
256        // HTMLNode.  (This is the "generic-type" of the Stream.Builder)
257        //
258        // FINALLY: Java Generics don't actually work as well as you think.  The following array
259        //          of Stream.Builder is NEITHER a raw-type, nor is there an unchecked cast...
260        //          It's the "Array" of "Generic Classes" that makes the compiler-break.  This is
261        //          IMHO, a Java-Bug of sorts.
262        //
263        // SIMPLY-PUT: If you create an "Array of Generics" the whole compile-time type-checking
264        //             thing (which is all a generic is) just gives up...
265        @SuppressWarnings({"unchecked", "rawtypes"})
266        Stream.Builder<Vector<HTMLNode>>[] bArr = new Stream.Builder[sectionNames.length];
267
268        for (int i=0; i < bArr.length; i++) bArr[i] = Stream.builder();
269
270        // Always skip the first row in the HTML table, it is just the header row.
271        if (iter.hasNext()) iter.next();
272
273        int numRearranged = 0;
274
275        // Just iterate the rows of the table, and sort them into the sections/buckets requested
276        while (iter.hasNext())
277        {
278            // get the next table-row from the Iterator.  The Iterator has already had its cursor
279            // restricted to just the summary-section requested by the user.  The first row (title)
280            // was skipped before starting the loop.
281            Vector<HTMLNode> tableRow = iter.next();
282
283            // The signature as a String.  The "Util" method just removes all HTML TagNode's that
284            // (and any CommentNode's) in the Vector, and then concatenates the remaining nodes into
285            // a String.
286            String rowStr = Util.textNodesString(tableRow);
287
288            // The 'placement' integer is what the user returns.  It tells which of the arrays
289            // to save the table-row.
290            int placement = 0;
291
292            // Dummy-variable for "Generic parameters"  The 
293            Vector<String> GP = new Vector<>();
294
295            // Use the user-provided lambda function (the sorter[arrPos]), to get the section-number
296            // where this table-row should be placed.
297            // 
298            // NOTE: This whole thing should actually be just a few lines, but it is the error
299            //       reporting that is making this much longer.
300            Field f=null;   Method m=null;  Constructor c=null;
301            try
302            {
303                switch (fcm)
304                {
305                    case Field:
306                        f = JavaDocHTMLFile.parseField(rowStr, pr.srcCodeFileName, GP);
307                        break;
308                    case Constructor:
309                        c = JavaDocHTMLFile.parseConstructor(rowStr, pr.srcCodeFileName, GP);
310                        break;
311                    case Method:
312                        m = JavaDocHTMLFile.parseMethod(rowStr, pr.srcCodeFileName, GP);
313                        break;
314
315                    default: throw new UnreachableError();
316                }
317            }
318            catch (JavaDocHTMLParseException e)
319            {
320                throw new SummaryRearrangeException(
321                    "While parsing the " + fcm + " list in the Summaries Section for File:\n" +
322                    pr.jdFileName + "\nOne of the HTML Summary Signatures failed to parse.  " +
323                    "See the Throwable.getCause() for more details.", e
324                );
325            }
326
327            // Now invoke the user provided sorter-function (it returns an integer)
328            try
329            {
330                switch (fcm)
331                {
332                    case Field:         placement = sorter.applyAsInt(f); break;
333                    case Constructor:   placement = sorter.applyAsInt(c); break;
334                    case Method:        placement = sorter.applyAsInt(m); break;
335                    default: throw new UnreachableError();
336                }
337            }
338            catch (ClassCastException e)
339            {
340                throw new SummaryRearrangeException(
341                    "While parsing the " + fcm + " list in the Summaries Section for File:\n" +
342                    pr.jdFileName + "\nThere was a ClassCastException.  Likely the sorting " +
343                    "function provided during configuration was not designed to accept the " +
344                    "right class.  You must provided a ToIntFunction<" + fcm + ">.  Please " +
345                    "see the Throwable.getCause() for more details."
346                );
347            }
348            catch (Exception e)
349            {
350                throw new SummaryRearrangeException(
351                    "While parsing the " + fcm + " list in the Summaries Section for File:\n" +
352                    pr.jdFileName + "\nThere was an exception.  The user-provided " +
353                    "ToIntFunction<" + fcm + "> method 'applyAsInt(" + fcm + ") did not complete.  "+
354                    "Please see the Throwable.getCause() for more details."
355                );
356            }
357
358            // The user (lambda-function) may have made a mistake (next 2 exception checks)
359            if (placement < 0) throw new SummaryRearrangeException(
360                "While parsing the " + fcm + " list in the Summaries Section for File:\n" +
361                pr.jdFileName + "\nOne of the values returned by the user-provided " +
362                "sorting-function was negative.  The value returned was [" + placement + "].  " +
363                "The sorter is used to place HTML table-rows into the appropriate sections of " +
364                "the " + fcm + " Summaries, and cannot be negative."
365            );
366
367            if (placement >= bArr.length) throw new SummaryRearrangeException(
368                "While parsing the " + fcm + " list in the Summaries Section for File:\n" +
369                pr.jdFileName + "\nOne of the values returned by the user-provided " +
370                "sorting-function was greater than the number of categories that were provided.  " +
371                "The value returned was [" + placement + "], but the number of categories is " +
372                bArr.length + ".  The sorter is used to place HTML table-rows into the appropriate " +
373                "sections of the " + fcm + " Summaries, and must be less than " + bArr.length
374            );
375
376            // Now place the table-row Vector into the "bucket" or "section" that was just returned
377            // by the user provided sorting-function / lambda.
378            bArr[placement].accept(tableRow);
379
380            // Keep a count of how many of these happened.  This count is printed at the end of
381            // this method.
382            numRearranged++;
383        }
384    
385        // The new HTML table rows will be placed here.
386        Vector<HTMLNode> tableBody = new Vector<>();
387
388        // Now build the new table-body.
389        for (int i=0; i < bArr.length; i++)
390            tableBody.addAll(
391                makeRows(
392                    (Vector<HTMLNode>[]) bArr[i].build().toArray(Vector[]::new),
393                    sectionNames[i], titleRow
394                ));
395
396        // Remove the table-caption
397        int numRemoved = TagNodeRemoveInclusive.first(pr.fileVec, summaryDP.start, -1, "caption");
398
399        // Replace the old table body with the new one that was just computed
400        Util.replaceRange(pr.fileVec, summaryDP.start+1, summaryDP.end-numRemoved, tableBody);
401
402        if (pr.sw != null) pr.sw.println(
403            "\tRearranged " + C.BBLUE + StringParse.zeroPad(numRearranged) + C.RESET + " " +
404            fcm.toString() + "(s) in the " +
405            C.BCYAN + fcm.toString() + " Summary Section." + C.RESET
406        );
407    }
408
409    // ********************************************************************************************
410    // ********************************************************************************************
411    // HTML Generation Part
412    // ********************************************************************************************
413    // ********************************************************************************************
414
415    private static final int FIELD_METH_NAME_POS = 21;
416
417    private static final Vector<HTMLNode> titleRowMethodOrField = HTMLPage.getPageTokens(
418        "<TR><TD COLSPAN=2>&nbsp;</TD></TR>\n" +
419        "<TR><TH CLASS=RSUMM COLSPAN=2><SPAN>TEXT</SPAN></TH></TR>\n" +
420        "<TR>\n" +
421        "\t<TH class=\"colFirst\" scope=\"col\" STYLE=\"white-space: nowrap;\">Modifier and Type</TH>\n" +
422        "\t<TH class=\"colSecond\" scope=\"col\">F_OR_M</TH>\n" +
423        "</TR>\n",
424        false
425    );
426
427    private static final Vector<HTMLNode> titleRowConstructor = HTMLPage.getPageTokens(
428        "<TR><TD>&nbsp;</TD></TR>\n" +
429        "<TR><TH CLASS=RSUMM><SPAN>TEXT</SPAN></TH></TR>\n" +
430        "<TR><TH class=\"colFirst\" scope=\"col\">Constructor</TH></TR>\n",
431        false
432    );
433
434    private static final TextNode NEWLINE = new TextNode("\n");
435
436    private static final Vector<HTMLNode> makeRows
437        (Vector<HTMLNode>[] rows, String sectionName, Vector<HTMLNode> titleRow)
438    {
439        Vector<HTMLNode>    ret         = new Vector<>();
440        boolean             altOrRow    = true;
441
442        titleRow.setElementAt(new TextNode(sectionName), 9);
443        ret.addAll(titleRow);
444
445        for (Vector<HTMLNode> row : rows)
446        {
447            int pos = InnerTagFind.first
448                (row, "tr", "class", TextComparitor.EQ, "altColor", "rowColor");
449
450            TagNode tn = (TagNode) row.elementAt(pos);
451
452            tn = tn.setCSSClasses(null, false, altOrRow ? "altColor" : "rowColor");
453            row.setElementAt(tn, pos);
454            altOrRow = ! altOrRow;
455
456            ret.addAll(row);
457            ret.add(NEWLINE);
458        }
459
460        return ret;
461    }
462}