001package Torello.HTML.Tools.JavaDoc;
002
003import java.util.*; // TreeMap, Iterator, ArrayList...
004
005import java.io.File;
006import java.lang.reflect.Parameter;
007import java.lang.reflect.Modifier;
008
009import Torello.Java.*; // StrCmpr, StrCSV, FileRW
010
011import Torello.Java.Additional.Ret3;
012import Torello.Java.Function.IntTFunction;
013import Torello.Java.Shell.C;
014
015/**
016 * <B STYLE='color:darkred;'>Process Java Doc Web-Page:</B>
017 * 
018 * Loads a user-provided {@code SummarySorter} class-file from disk.
019 * 
020 * <EMBED CLASS="external-html" DATA-FILE-ID=PKG_PRIVATE_MSG>
021 * <EMBED CLASS="external-html" DATA-FILE-ID=REARRANGE_SUMM>
022 */
023public class RetrieveSummarySorters
024{
025    private RetrieveSummarySorters() { }
026
027    // ********************************************************************************************
028    // ********************************************************************************************
029    // Static-Inner Data Record-Classes.  Two Private-Classes, and One Package-Visible Class
030    // ********************************************************************************************
031    // ********************************************************************************************
032
033
034    // The 'Sorter' is a strictly-internal (private) inner-class that is built directly out of an
035    // instance of "java.lang.reflect.Method" that is extracted from the user-provided class-file
036    // "upgrade-files/SummarySorters.class"  Instances of this class are **ONLY TEMPORARILY USED**
037
038    private static final class Sorter
039    {
040        public final String ciet;
041        public final java.lang.reflect.Method sorter;
042        public final Entity entity;
043
044        Sorter(String ciet, java.lang.reflect.Method sorter, Entity entity)
045        {
046            this.ciet   = ciet;     // The **SIMPLE NAME** of the class/type
047            this.sorter = sorter;   // The SECTION-TITLES for the "Entity Summary"
048            this.entity = entity;   // Which Summary-Section: Field Summaries, Method Summaries ...
049        }
050
051        public String toString()
052        {
053            return ciet + ": " + C.BCYAN + entity.toString() + C.RESET + " Summary Sorter\n\t" +
054                C.BGREEN + sorter.toString().trim() + C.RESET;
055        }
056    }
057
058
059    // Printer
060    private static final IntTFunction<String, String> PRINTER =
061        (int i, String s) -> "\n\t\"" + s + '\"';
062
063    // The 'Sorter' is a strictly-internal (private) inner-class that is built directly out of an
064    // instance of "java.lang.reflect.Method" that is extracted from the user-provided class-file
065    // "upgrade-files/SummarySorters.class"  Instances of this class are **ONLY TEMPORARILY USED**
066
067    private static final class SectionTitles
068    {
069        public final String     ciet;
070        public final String[]   sectionTitles;
071        public final Entity     entity;
072
073        public SectionTitles(String ciet, String[] sectionTitles, Entity entity)
074        {
075            this.ciet   = ciet;                 // The **SIMPLE NAME** of the class/type
076            this.sectionTitles = sectionTitles; // The SECTION-TITLES for the "Entity Summary"
077            this.entity = entity;               // Which Summary-Section: Field Summaries,
078                                                // Method Summaries ...
079        }
080
081        public String toString()
082        {
083            return ciet + ": " + C.BCYAN + entity.toString() + C.RESET +
084                " Summary Section-Titles (String[] Array):\n" + C.BYELLOW + "{" +
085                StrCSV.toCSV(sectionTitles, PRINTER, true, null) +
086                "\n};" + C.RESET;
087        }
088    }
089
090    // This is the class
091    static final class SorterAndTitles
092    {
093        // This is the CIET-Name (a.k.a. "The Class").  If the Methods, Fields or Constructors of
094        // java.lang.Object were being sorted ==> This would be "Object"
095        public final String ciet;
096
097        // These are a list of "Titles" that are placed inside the Field Summary Table (or Method
098        // Summary Table ==> Depending upon *WHICH* entity is being sorted.)
099        public final String[] sectionTitles;
100
101        // The User-Provided Sorting Method ==> that has been extracted from the Use-Provided
102        // pkg/upgrade-files/SummarySorter.class File
103        public final java.lang.reflect.Method sorter;
104
105        // Identifies whether Fields, Methods, Constructor, etc... (again *WHICH* summaries may be
106        // sorted by the Titles/Sorter pair)
107        public final Entity entity;
108
109        SorterAndTitles
110            (String ciet, String[] sectionTitles, java.lang.reflect.Method sorter, Entity entity)
111        {
112            this.ciet           = ciet;
113            this.sectionTitles  = sectionTitles;
114            this.sorter         = sorter;
115            this.entity         = entity;
116        }
117
118        public String toString()
119        {
120            return C.BCYAN + ciet + C.RESET + " [" + C.BYELLOW + entity + C.RESET + "]\n" +
121                "\tSorter Method:\n" +
122                "\t\t" + C.BRED + sorter.toString().trim() + C.RESET + "\n" +
123                "\tSection Titles (String[] Array):\n" +
124                C.BYELLOW + "\t{" +
125                    StrIndent.indentTabs
126                        (StrCSV.toCSV(sectionTitles, PRINTER, true, null), 1) + "\n" +
127                "\t};" + C.RESET;
128        }
129    }
130
131
132    // ********************************************************************************************
133    // ********************************************************************************************
134    // Testing for this class.  It is private because it is only used for testing
135    // ********************************************************************************************
136    // ********************************************************************************************
137
138
139    // This is used for debugging.
140    private static final String[] packages = new String[]
141    {
142        "Torello.HTML",
143        "Torello.HTML.NodeSearch",
144        "Torello.HTML.Tools.Images",
145        "Torello.HTML.Tools.JavaDoc",
146        "Torello.HTML.Tools.NewsSite",
147        // "Torello.HTML.Tools.SearchEngines",
148        "Torello.Java",
149        "Torello.Java.Additional",
150        "Torello.Java.Function",
151        "Torello.Languages",
152    };
153
154    private static void main(String[] args) throws Exception
155    {
156        Upgrade u = new Upgrade("javadoc/", "").turnOnVerboseProcessPrint();
157
158        for (String pkg : packages)
159        {
160            System.out.println(pkg);
161
162            TreeMap<String, ArrayList<SorterAndTitles>> map = retrieve(pkg, u);
163
164            if (map == null)
165            {
166                System.out.println("There were errors, a null map was returned.");
167                System.exit(1);
168            }
169
170            for (ArrayList<SorterAndTitles> bothAL : map.values())
171
172                for (SorterAndTitles both : bothAL)
173
174                    System.out.println(both.toString());
175
176            // if (! Q.YN("continue?")) System.exit(0);                
177        }
178    }
179
180
181    // ********************************************************************************************
182    // ********************************************************************************************
183    // Top Level Processor "Main" Method - *ALL* Methods in this class are *PRIVATE* except this
184    // ********************************************************************************************
185    // ********************************************************************************************
186
187
188    // THIS METHOD IS THE PUBLIC ACCESSOR METHOD TO THIS CLASS, THE OTHERS ARE PRIVATE
189    // MESSAGER:
190    //  1) SETS:        processorName (1st line) and fileName (subsequent method)
191    //  2) RETURNS:     null on errors
192    //  3) INVOKES:     errorExitingNow (but calls others as well)
193    //  4) INVOKED-BY:  Only in MainFilesProcessor, top-part of main loop
194
195    static TreeMap<String, ArrayList<SorterAndTitles>> retrieve(String packageName, Upgrade u)
196    {
197        Messager.setProcessorName("RetrieveSummarySorters");
198
199        // IMPORTANT: This used to be a "Ret2", but that has since been changed to allow for
200        //            returning the actual file-name where the "SummarySorters.class" was found.
201        //            This File-Name is actually very-useful for error-reporting messages.
202
203        Ret3<java.lang.reflect.Method[], java.lang.reflect.Field[], String> sortersAndSections =
204            loadClassFileMethodsAndFields(packageName, u);
205
206        // NOTE: The length of these *RETURN ARRAYS* are *NOT* identical.  The following nine
207        //       methods are automatically / synthetically added by Java.
208        //
209        // public final native void java.lang.Object.wait(long) - java.lang.InterruptedException
210        // public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
211        // public final void java.lang.Object.wait() throws java.lang.InterruptedException
212        // public boolean java.lang.Object.equals(java.lang.Object)
213        // public java.lang.String java.lang.Object.toString()
214        // public native int java.lang.Object.hashCode()
215        // public final native java.lang.Class java.lang.Object.getClass()
216        // public final native void java.lang.Object.notify()
217        // public final native void java.lang.Object.notifyAll()
218
219        // NOTE: At the present time, there is no way to tell the difference between 'null' from an
220        //       error, and 'null' resulting from not being able to find a SummarySorter.class file
221        //       That is the point of the messager, the messager "knows" if an error has been
222        //       reported.
223
224        if (sortersAndSections == null) return null;
225
226        // The list of all CIET's in the package "package name"
227        // NOTE: This throws UnreachableError if it doesn't exits
228        TreeSet<String> packageCIETList = u.getPackageTypesList(packageName);
229
230        // Create all the "Sorter Methods", and put them into a TreeMap.  Note the String that
231        // functions as the TreeMap-Key is the CIET-Type *NAME*
232        //
233        // This method does a lot of Messager.errorContinue
234
235        TreeMap<String, ArrayList<Sorter>> sorters = checkSortersAndBuildTreeMap
236            (sortersAndSections.a, packageCIETList);
237
238        // Create all of the "Section Titles", and put them into a TreeMap.  Again, the
239        // TreeMap String-Key is the CiET-Type *NAME*
240
241        TreeMap<String, ArrayList<SectionTitles>> sectionTitles =
242            checkSectionTitlesListsAndBuildTreeMap
243                (sortersAndSections.b, sortersAndSections.c, packageCIETList);
244
245        // NOTE: All of those Classes that were just created are garbage-collected quickly.  There
246        //       isn't a "more efficient" way to really do this, if code-readability is important.
247        //       All those classes are - are lists of strings.
248        //
249        // ==> This method Matches-Up the Sorters to the Titles into which these sorters are
250        //     sorting!  These interim classes are just discarded.
251
252        return matchSortersAndTitles(sectionTitles, sorters);
253    }
254
255    // Short Exception-Message Printing-Method Helper
256    private static String BASE_MSG
257        (String sectionsArrName, java.lang.reflect.Method sorter, Parameter[] pArr)
258    {
259        return 
260            "While attempting to find a Sorter Match for [" + sectionsArrName + "], " +
261            "Found Sorter Method:\n\tpublic static " +
262            sorter.getReturnType().getSimpleName() + ' ' + sorter.getName() +
263            "(" + StrCSV.toCSV(pArr, (i, p) -> p.getType().getSimpleName(), true, null) +
264                ") { ... }\n" +
265            "If this is a Summary Sorting Method then it ";
266    }
267
268
269    // ********************************************************************************************
270    // ********************************************************************************************
271    // Read all **PUBLIC** and **STATIC** Fields and Methods inside the Summary-Sorter Class-File
272    // ********************************************************************************************
273    // ********************************************************************************************
274
275
276    // NOTE: Things were simpler when this was a "Ret2" instance that simply returned two instances
277    //       of arrays.  This third return-value that made this a "Ret3" is a String, and all that
278    //       String is - is the File-Name of the '.class' file where that '.class' file was
279    //       ultimately found.
280    //
281    // @return This method returns two arrays
282    //  ARRAY 1: Each Method in the "SummarySorter.class" File that was both 'public' and 'static'
283    //  ARRAY 2: Each Field in the "SummarySorter.class" file that was also 'public' and 'static'
284    //
285    // NOTE: This method will return 'null' if such a file is never found or identified anywhere
286    //       inside any of the "Upgrade.rootSourceFileDirectories"
287    //
288    // MESSAGER:
289    //  THIS METHOD IS PRIVATE, AND ONLY INVOKED ABOVE!  DOES NOT SET 'processorName'
290    //  2) SETS: file-name on the fourth line.
291    //  3) USES: 'Messager.errorExitingNow' (NULL RETURN)
292
293    private static Ret3<java.lang.reflect.Method[], java.lang.reflect.Field[], String>
294        loadClassFileMethodsAndFields(String packageName, Upgrade u)
295    {
296        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
297        // FIND IF "upgrade-files/SummarySorter.class" EXISTS for PACKAGE anywhere in CLASSPATH
298        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
299
300        // If the user has created a 'SummarySorter.class' file for the "packageName" package, 
301        // it would be in an 'upgrade-files/' sub-directory for the/a Source-Code directory for
302        // that Package.  Remember, it is theoretically possible for there to be MORE THAN ONE
303        // Root-Source-Code Directory for any one given Java Package.
304        //
305        // The Java Compiler calls the suite of directories of "src/main/java" the "ClassPath".
306        //  In Java HTML, there is a String[] Array called "Upgrade.rootSourceFileDirectories"
307
308        String summarySorterClassPartialFileName = packageName.replace(".", File.separator) +
309            File.separator + "upgrade-files" + File.separator + "config-classes" + File.separator +
310            "SummarySorters.class";
311
312        // Search the CLASS-PATH looking for: "full/package/name/upgrade-files/SummarySorter.class"
313        // true => fileOrDirectory (a file)
314        String summarySorterClassFileName = HELPER.findFileNameInClassPath
315            (summarySorterClassPartialFileName, u.rootSourceFileDirectories, true);
316
317        if (summarySorterClassFileName != null) 
318        {
319            Messager.println(
320                "Found 'SummarySorters' Configuration-Class: " + 
321                "[" + C.BYELLOW + summarySorterClassFileName + C.RESET + "]"
322            );
323
324            Messager.setCurrentFileName
325                (summarySorterClassFileName, "Summary Sorter Configuration Class");
326        }
327        else
328        {
329            // Finish the "Opening Statement" from the 'm.print' before the previous loop started
330            if (Messager.isVerbose())
331                Messager.println(" No Summary Sorter Configuration Class Found");
332
333            return null;
334        }
335
336
337        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
338        // Load the 'SummarySorters' Class into instance of java.lang.Class - TWO POSSIBLE NAMES
339        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
340
341        Class<?> summarySorterClass = null;
342
343        // These are the two user options for the name of the package.
344        String[] cNamesArr = new String[] { packageName + ".SummarySorters", "SummarySorters" };
345
346        try 
347            { summarySorterClass = FileRW.readClass(summarySorterClassFileName, cNamesArr); }
348
349        catch (TypeNotPresentException e)
350        {
351            Messager.errorExitingNow(e, Messager.TNPE);
352            return null;
353        }
354
355        catch (Exception e)
356        {
357            Messager.errorExitingNow(
358                e,
359                "There was an unexpected exception while attempting to read the Summary-Sorter " +
360                "Class-File: [" + summarySorterClassFileName  + "]."
361            );
362
363            return null;
364        }
365
366
367        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
368        // RETURN THE TWO ARRAYS, AND EXIT
369        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
370
371        // Previous-Version of the LOC below:
372        // java.lang.reflect.Field[]   possibleSectionTitles   = summarySorterClass.getFields();
373        // java.lang.reflect.Method[]  possibleSorters         = summarySorterClass.getMethods();
374
375        // The third-parameter to this Ret3 is a late-addition.  It is just returned here because
376        // that String can be very useful for output/messager error-printing.
377
378        return new Ret3<>(
379            summarySorterClass.getMethods(), summarySorterClass.getFields(),
380            summarySorterClassFileName
381        );
382    }
383
384
385    // ********************************************************************************************
386    // ********************************************************************************************
387    // Check Method[] for User-Errors, Convert into a TreeMap (using the CIET-NAME for a KEY)
388    // ********************************************************************************************
389    // ********************************************************************************************
390
391
392    private static final String SORTER_HEADER =
393        "Declared Sorter Method:\n    [" + C.BCYAN + "SORTER" + C.RESET + "]\n";
394
395
396    // THIS METHOD IS PRIVATE, IS ONLY CALLED FROM WITHIN THIS CLASS
397    // This method calls the private-method directly beneath this one as a helper to
398    // actually build the 'Sorter' (static-inner class) instance.
399    //
400    // MESSAGER:
401    //  1) USES: *ONLY* 'errorContinue'
402    //  2) RETURN: always returns VALID INSTANCE
403    //  3) DOES NOT CHANGE PROCESSOR NAME (it is done in the public accessor method at top)
404
405    private static TreeMap<String, ArrayList<Sorter>> checkSortersAndBuildTreeMap
406        (java.lang.reflect.Method[] sorters, TreeSet<String> packageCIETList)
407    {
408        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
409        // This TreeMap is returned to the user.  Iterate each sorter and put into the TreeMap
410        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
411
412        TreeMap<String, ArrayList<Sorter>> allPkgSorters = new TreeMap<>();
413
414        for (int i=0; i < sorters.length; i++)
415        {
416            java.lang.reflect.Method    sorter  = sorters[i];
417            String                      name    = sorter.getName();
418
419            // If the method-name does not end with "$Sorter", ignore it completely
420            if (! name.endsWith("$Sorter")) continue;
421
422
423            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
424            // Error-Check the Method: *MUST* be static, *MUST* return 'int', *MUST* have 1 param
425            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
426
427            // Sorter-Method must be static, or there is no way to invoke the method.
428            if ((sorter.getModifiers() & Modifier.STATIC) == 0)
429            {
430                Messager.errorContinue(
431                    SORTER_HEADER.replace("SORTER", sorter.toString()) +
432                    "Does not have the 'static' modifier attached to it."
433                );
434
435                continue; // Non-Fatal Error, Skip this sorter-method
436            }
437
438            // Sorter-Method must return an 'int' primitive
439            if (! sorter.getReturnType().equals(int.class))
440            {
441                Messager.errorContinue(
442                    SORTER_HEADER.replace("SORTER", sorter.toString()) +
443                    "Returns [" + sorter.getReturnType().getSimpleName() + "], but sorters " +
444                    "have to return primitive-type 'int'."
445                );
446
447                continue; // Non-fatal Error, Skip this sorter-method
448            }
449
450            Parameter[] params = sorter.getParameters();
451
452            // Sorter Method must have *EXACTLY* 1 input parameter.
453            if (params.length != 1)
454            {
455                Messager.errorContinue(
456                    SORTER_HEADER.replace("SORTER", sorter.toString()) +
457                    "Seems to have [" + params.length + "] input parameters.  However " +
458                    "sorter methods must have precisely 1."
459                );
460
461                continue; // Non-fatal Error, Skip this sorter-method
462            }
463
464            Class<?> paramClass = params[0].getType();
465
466            // Sorter-Method single-parameter must inherit 'Torello.HTML.Tool.JavaDoc.Declaration'
467
468            if (! Declaration.class.isAssignableFrom(paramClass))
469            {
470                Messager.errorContinue(
471                    SORTER_HEADER.replace("SORTER", sorter.toString()) +
472                    "Has a parameter that accepts an unknown type.  Sorters are expected " +
473                    "to receive exactly one parameter, and that parameter's type must extend:\n" +
474                    "    [Torello.HTML.Tools.JavaDoc.Declaration].\n" +
475                    "This sorter has a parameter of type:\n" +
476                    "    [" + C.BCYAN + paramClass.getCanonicalName() + C.RESET + "]"
477                );
478
479                continue; // Non-Fatal, Skip
480            }
481
482
483            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
484            // Convert this *DATA* into an instance of "Sorter."
485            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
486
487            // "$Sorter".length() ==> 7
488            String typeName = name.substring(0, name.length() - 7);
489
490            Sorter s = getSorter(typeName, sorter, paramClass);
491
492            // The "getSorter" method will return null if an error was logged to the messager.
493            if (s == null) continue;
494
495
496            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
497            // Check to ensure that the "class" specified by the sorter's name is on the list
498            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
499
500            if (! packageCIETList.contains(s.ciet))
501            {
502                Messager.errorContinue(
503                    "Sorter-Method Named: [" + C.BGREEN + name + C.RESET + "]\n" +
504                    "Specifies a Type / Class " +
505                        "[" + C.BCYAN + s.ciet + C.RESET + "] that is not " +
506                    "among the listed Type-Names in the HTML File 'package-summary.html' for " +
507                    "this package"
508                );
509
510                continue;
511            }
512
513
514            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
515            // Add the sorter to the list of sorters for this 'Type' (There may be more than one)
516            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
517            //
518            // REMEMBER:  Multiple-Sorters may be assigned to the same CIET.  There may be one for
519            //            Fields, one for Methods, etc..
520            //
521            // THEREFORE: This is a "TreeMap" that contains "ArrayLists" as values.
522
523            ArrayList<Sorter> cietSortersList = allPkgSorters.get(s.ciet);
524
525            // The ArrayList is null means this CIET has not had any Sorters assigned to it yet.
526            // Since this is the first Sorter added for the CIET, put the sorter into a *NEW*
527            // ArrayList, and put that into the TreeMap, under key=cietName.
528
529            if (cietSortersList == null)
530                allPkgSorters.put(s.ciet, cietSortersList = new ArrayList<>());
531
532            cietSortersList.add(s);
533        }
534
535        return allPkgSorters;
536    }
537
538
539    // ********************************************************************************************
540    // ********************************************************************************************
541    // Convert java.lang.reflect.Method into a Sorter
542    // ********************************************************************************************
543    // ********************************************************************************************
544
545
546    // Convert the information into an instance of the private-static inner class "Sorter"
547    // 
548    // name:        The name of the method, **LESS** the "$Sorter" substring at the end of it
549    // method:      The java.lang.reflect Method
550    // paramClass:  The java.lang.Class of the method's **ONLY** parameter
551    //
552    // THIS METHOD IS PRIVATE, IS ONLY CALLED FROM WITHIN THIS CLASS
553    //
554    // MESSAGER:
555    //  1) USES: *ONLY* 'errorContinue'
556    //  2) RETURNS: null only on error
557    //  3) DOES NOT CHANGE PROCESSOR NAME (it is done in the public accessor method at top)
558
559    private static Sorter getSorter
560        (String name, java.lang.reflect.Method method, Class<?> paramClass)
561    {
562        // This is the sorter-type, computed as an instance of the enum 'Entity'
563        Entity entity = null;
564
565        // This is *ALSO* the sorter-type, computed as an instance of java.lang.Class
566        Class<?> expectedParamClass = null;
567
568        // This is a minor issue, if the user *DOES NOT* specify the type of sorter inside the
569        // method's name, then it is presumed to be a Method-Sorter.  If so, the ending '$'
570        // specifier doesn't have to be removed...  All other's need the trailing "$TYPE" removed
571        // from the method-name in order to figure out the CIET-Name.
572        //
573        // NOTE: At this point, the "$Sorter" String-Ending has *ALREADY BEEN REMOVED*
574
575       boolean removeIt = true;
576
577    
578        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
579        // Use the *NAME* of the method to figure out what type of Sorter it is.
580        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
581
582        if (StrCmpr.endsWithXOR(name, "$Method", "$M"))
583        {
584            entity = Entity.METHOD;
585            expectedParamClass = Torello.HTML.Tools.JavaDoc.Method.class;
586        }
587
588        else if (StrCmpr.endsWithXOR(name, "$Field" , "$F"))
589        {
590            entity = Entity.FIELD;
591            expectedParamClass = Torello.HTML.Tools.JavaDoc.Field.class;
592        }
593
594        else if (StrCmpr.endsWithXOR(name, "$Constructor", "$C"))
595        {
596            entity = Entity.CONSTRUCTOR;
597            expectedParamClass = Torello.HTML.Tools.JavaDoc.Constructor.class;
598        }
599
600        else if (StrCmpr.endsWithXOR(name, "$EnumConstant","$EC"))
601        {
602            entity = Entity.ENUM_CONSTANT;
603            expectedParamClass = Torello.HTML.Tools.JavaDoc.EnumConstant.class;
604        }
605
606        // *** *** THIS IS NOT IMPLEMENTED
607
608        /*
609        else if (StrCmpr.endsWithXOR(name, "$AnnotationElem", "$AE"))
610        {
611            entity = Entity.ANNOTATION_ELEM;
612            expectedParamClass = Torello.HTML.Tools.JavaDoc.AnnotationElem.class;
613        }
614        */
615
616        else if (StrCmpr.endsWithXOR(name, "$InnerClass", "$IC"))
617        {
618            entity = Entity.INNER_CLASS;
619            expectedParamClass = Torello.HTML.Tools.JavaDoc.NestedType.class;
620        }
621
622        // This likely means that no sorter-type was specified, and it should be presumed to be
623        // a Method-Sorter.
624
625        else
626        {
627            entity              = Entity.METHOD;
628            expectedParamClass  = Torello.HTML.Tools.JavaDoc.Method.class;
629
630            // Remember: It wasn't specified, the "$TYPE" doesn't need to be removed from the
631            // name-String in order to find out the CIET-Name String.
632
633            removeIt = false;
634        }
635
636        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
637        // Compute the name of the CIET/Type
638        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
639
640        // If the user has not specified what KIND of "Summary-Sorter" this is (Field, Method,
641        // Constructor, etc...), then the identifier-string "$TYPE" doesn't need to be removed
642        // (because the user didn't include one).  *HOWEVER*, if one was used, it hs to be
643        // included in order to retrieve the *NAME* of the CIET/Type
644
645        if (removeIt) name = name.substring(0, name.lastIndexOf('$'));
646
647        // If the CIET/Type that the user was specifying was an inner-class, the separator
648        // that was used between the Enclosing-Classes and Inner-Classes names needs to be
649        // changed from a '$' to a '.'
650
651        name = name.replace('$', '.');
652
653
654        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
655        // Last Check: Ensure the method's single input-parameter is consistent with it's 'Entity'
656        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
657
658        if (! paramClass.isAssignableFrom(expectedParamClass))
659        {
660            Messager.errorContinue(
661                SORTER_HEADER.replace("SORTER", method.toString()) +
662                "Does not have a parameter that may receive a " +
663                "[" + expectedParamClass.getCanonicalName() + "]"
664            );
665
666            return null;
667        }
668
669
670        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
671        // Return a new 'Sorter' instance.  Sorter is a private-static inner-class of this class
672        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
673
674        return new Sorter(name, method, entity);
675    }
676
677
678    // ********************************************************************************************
679    // ********************************************************************************************
680    // Check Field[] for User-Errors, Convert into a TreeMap (using CIET-NAME as KEY)
681    // ********************************************************************************************
682    // ********************************************************************************************
683
684
685    // The Parameters:
686    //
687    // @param classFileName - The actual file-system file-name that contained the '.class' file
688    //
689    // THESE PARAMETERS ARE JUST USED FOR ERROR-MESSAGE REPORTING
690    //
691    // MESSAGER:
692    //  1) Method is PRIVATE, does not change 'processorName' or 'fileName'
693    //  2) Always returs valid TreeMap instance - only uses 'errorContinue'
694
695    private static TreeMap<String, ArrayList<SectionTitles>> checkSectionTitlesListsAndBuildTreeMap
696        (java.lang.reflect.Field[] fArr, String classFileName, TreeSet<String> packageCIETList)
697    {
698        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
699        // This the returned TreeMap.  Iterate each Titles String[]-Array and put into the TreeMap
700        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
701
702        TreeMap<String, ArrayList<SectionTitles>> allPkgSectionTitles = new TreeMap<>();
703
704        for (int i=0; i < fArr.length; i++)
705        {
706            java.lang.reflect.Field field = fArr[i];
707
708            String name = field.getName();
709
710            if (! name.endsWith("$Sections")) continue;
711
712            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
713            // Error-Check the Array: *MUST* be static, *MUST* be a 'String[]' instance
714            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
715
716            if (! String[].class.equals(field.getType()))
717            {
718                Messager.errorContinue(
719                    "Section-Titles Field [" + C.BGREEN + name + C.RESET + "], is not declared " +
720                    "as a String[] Array:\n\t[" + C.BCYAN + field.toString() + C.RESET + "]"
721                );
722
723                continue;
724            }
725
726            if ((field.getModifiers() & Modifier.STATIC) == 0)
727            {
728                Messager.errorContinue(
729                    "the Section-Titles String[] Array Field [" + name + "], does not have " +
730                    "the modifier 'static' applied.  All Section-Title String-Array's must be " +
731                    "declared static."
732                );
733
734                continue;
735            }
736
737
738            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
739            // Use the *NAME* of the String[]-Array Field to figure out what KIND of Title's it is
740            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
741
742            // "$Sections".length()  ==> 9
743            name = name.substring(0, name.length() - 9);
744
745            // Not all Section-Titles String[]-Array's specify a Type - and if they don't, they are
746            // presumed to be Method-Summary Section-Title's.  If they don't, the specifier does
747            // not need to be removed from the name of the String[]-Array Field to compute the CIET
748            // Name.
749
750            boolean removeIt = true;
751
752            // This 'if-branch' statement is designed to compute this type, as an instance of the
753            // enum "Entity"
754
755            Entity entity = null;
756
757            if (StrCmpr.endsWithXOR(name, "$Method", "$M"))
758                entity = Entity.METHOD;
759
760            else if (StrCmpr.endsWithXOR(name, "$Field" , "$F"))
761                entity = Entity.FIELD;
762
763            else if (StrCmpr.endsWithXOR(name, "$Constructor", "$C"))
764                entity = Entity.CONSTRUCTOR;
765
766            else if (StrCmpr.endsWithXOR(name, "$EnumConstant","$EC"))
767                entity = Entity.ENUM_CONSTANT;
768
769            // *** *** This is NOT implemented
770            /*
771            else if (StrCmpr.endsWithXOR(name, "$AnnotationElem", "$AE"))
772                entity = Entity.ANNOTATION_ELEM;
773            */
774
775            else if (StrCmpr.endsWithXOR(name, "$InnerClass", "$IC"))
776                entity = Entity.INNER_CLASS;
777
778            // When nothing is specified, presume that this is a "Method Summary Sorter"
779            else
780            {
781                entity = Entity.METHOD;
782
783                // Remember: It wasn't specified, so the "$TYPE" doesn't need to be removed from
784                // the name-String in order to find out the CIET-Name String.
785    
786                removeIt = false;
787            }
788
789
790            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
791            // Compute the name of the CIET/Type
792            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
793
794            // If the user has not specified what KIND of "SectionTitles" this is (Field, Method,
795            // Constructor, etc...), then the identifier-string "$TYPE" doesn't need to be removed
796            // (because the user didn't include one).  *HOWEVER*, if one was used, it hs to be
797            // included in order to retrieve the *NAME* of the CIET/Type
798
799            if (removeIt) name = name.substring(0, name.lastIndexOf('$'));
800
801            // If the CIET/Type that the user was specifying was an inner-class, the separator
802            // that was used between the Enclosing-Classes and Inner-Classes names needs to be
803            // changed from a '$' to a '.'
804
805            name = name.replace('$', '.');
806
807
808            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
809            // Retrieve the String[]-Array Field from "SummarySorters" class into a local-variable
810            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
811
812            // The above cast has already been checked, and should never fail...
813            //
814            // NOTE: There are some Error-Messager checks done in this method.  Because the code
815            //       is used elsewhere, this "Read the Field Value" (which literally is just one
816            //       line of code - inside of a "Try-Catch" block!) ==> **IS DONE INSIDE HELPER**
817        
818            String[] sectionTitles = (String[]) HELPER.getStaticFieldEvaluation
819                (field, classFileName);
820
821            // NOTE: If there were errors reading the Field in the Previous Method, the Messager
822            //       will have already printed plenty of polite and friendly text to the end-user.
823
824            if (sectionTitles == null) return null;
825
826
827            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
828            // Construct a 'SectionTitles' (a local, private-static inner-class), put into TreeMap
829            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
830
831            ArrayList<SectionTitles> sectionTitlesAL = allPkgSectionTitles.get(name);
832
833            if (sectionTitlesAL == null)
834                allPkgSectionTitles.put(name, sectionTitlesAL = new ArrayList<>());
835
836            sectionTitlesAL.add(new SectionTitles(name, sectionTitles, entity));
837        }
838
839        // Return the TreeMap.
840        return allPkgSectionTitles;
841    }
842
843
844    // ********************************************************************************************
845    // ********************************************************************************************
846    // findSorterMatch
847    // ********************************************************************************************
848    // ********************************************************************************************
849
850
851    // titles:      All Section-Title String[]-Array's retrieved from the SummarySorter.class file
852    // sorters:     All Sorter Method's retrieved from that class-file
853    // packageName: Name of User's Package where the "upgrade-files/SummarySorter.class" was found.
854    //
855    // MESSAGER:
856    //  1) Method is private, does not reset anything.  'processorName' and 'fileName' set above
857    //  2) Utilizes 'errorContinue' to possibly print out multiple problems
858    //  3) If there were errors, this method returns null.
859
860    private static TreeMap<String, ArrayList<SorterAndTitles>> matchSortersAndTitles(
861            TreeMap<String, ArrayList<SectionTitles>> titles,
862            TreeMap<String, ArrayList<Sorter>> sorters
863        )
864    {
865        TreeMap<String, ArrayList<SorterAndTitles>> ret = new TreeMap<>();
866
867        boolean errors = false;
868
869        // Each Package has several CIET/Types - this iterates each '.java' file in the package
870        for (ArrayList<SectionTitles> titlesAL : titles.values())
871        {
872            // NOTE: The 'sorters.remove' *MUST* be used, becaue at the end of this loop, that
873            //       TreeMap is checked for any non-empty array lists, and if they exist, they
874            //       are printed as "unmatched" using the messager.  If this isn't done, then some
875            //       of the error messages would be printed twice.
876
877            String                      ciet        = titlesAL.get(0).ciet;
878            ArrayList<Sorter>           sorterAL    = sorters.remove(ciet);
879            ArrayList<SorterAndTitles>  comboAL     = new ArrayList<>();
880
881            // This will logged by the error-logging check at the bottom.  It is altogether
882            // possible that the user didn't provide a sorter, so this could be null.  The loop
883            // beneath here throws NPE without this check.
884
885            if (sorterAL != null)
886
887                // Each CIET/Type - '.java' File - may have more than one Sumary-Sorter
888                // This iterates the 'Section Titles' ArrayList for one CIET/Type '.java' File
889
890                for (int i=0; i < titlesAL.size(); i++)
891                {
892                    SectionTitles titlesList = titlesAL.get(i);
893
894                    // NOW: Iterate the 'Sorters' ArrayList and check each 'Sorter' to match it to
895                    //      the correct 'SectionTitles'.
896
897                    SORTER_MATCH_LOOP:
898                    for (int j=0; j < sorterAL.size(); j++)
899                    {
900                        Sorter sorter = sorterAL.get(j);
901
902                        if (titlesList.entity == sorter.entity)
903                        {
904                            comboAL.add(
905                                new SorterAndTitles(
906                                    sorter.ciet, titlesList.sectionTitles, sorter.sorter,
907                                    sorter.entity
908                                )
909                            );
910
911                            titlesAL.remove(i--);
912                            sorterAL.remove(j--);
913                            break SORTER_MATCH_LOOP;
914                        }
915                    }
916                }
917
918
919            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
920            // Error-Checking: If ArrayLists's are *NON-EMPTY* it means they weren't matched-up!
921            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
922
923            if (titlesAL.size() > 0)
924            {
925                StringBuilder sb = new StringBuilder();
926
927                for (SectionTitles titlesList : titlesAL) sb.append(titlesList.toString() + '\n');
928
929                Messager.errorContinue(
930                    "For Class [" + C.BCYAN + ciet + C.RESET + "], there are Section-Titles " +
931                    "String[]-Array's that were not matched to any Sorter-Methods:\n" +
932                    sb.toString()
933                );
934
935                errors = true;
936            };
937
938            // Since "titlesAL" is used as the outer loop-iterator control-thingy, there is no
939            // way it could be null.  However, it is altogether possible that 'sorterAL' is null,
940            // so check for that first.
941
942            if ((sorterAL != null) && (sorterAL.size() > 0))
943            {
944                StringBuilder sb = new StringBuilder();
945
946                for (Sorter sorter : sorterAL) sb.append(sorter.toString() + '\n');
947
948                Messager.errorContinue(
949                    "For Class [" + C.BCYAN + ciet + C.RESET + "], there are Sorter Method's " +
950                    "that were not matched to any Section-Titles String[]-Array's:\n" +
951                    sb.toString()
952                );
953
954                errors = true;
955            }
956
957            if (comboAL.size() > 0) ret.put(ciet, comboAL);
958        }
959
960
961        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
962        // Last possible error - Since 'sorters' was not iterated, it has to be checked...
963        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
964
965        for (ArrayList<Sorter> sortersAL: sorters.values())
966
967            if (sortersAL.size() > 0)
968            {
969                StringBuilder sb = new StringBuilder();
970
971                for (Sorter sorter : sortersAL) sb.append(sorter.toString() + '\n');
972
973                Messager.errorContinue(
974                    "For Class [" + C.BCYAN + sortersAL.get(0).ciet + C.RESET + "], there are " +
975                    "Sorter Method's that were not matched to any Section-Titles " +
976                    "String[]-Array's:\n" +
977                    sb.toString()
978                );
979
980                errors = true;
981            }
982
983
984        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
985        // Return the result
986        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
987
988        return errors ? null : ret;
989    }
990}