001package Torello.HTML.Tools.JavaDoc;
002
003import java.util.regex.Pattern;
004import java.util.function.Consumer;
005import java.util.Optional;
006
007import Torello.Java.StringParse;
008import Torello.Java.StrCSV;
009
010import static Torello.Java.Shell.C.*;
011import static Torello.HTML.Tools.JavaDoc.PF.*;
012
013import com.github.javaparser.Range;
014import com.github.javaparser.Position;
015import com.github.javaparser.ast.NodeList;
016import com.github.javaparser.ast.Modifier;
017import com.github.javaparser.ast.expr.AnnotationExpr;
018import com.github.javaparser.ast.comments.JavadocComment;
019import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter;
020
021/**
022 * <B STYLE='color:darkred;'>Java Parser Bridge:</B>
023 * 
024 * Common-Root Ancestor Class of all Bridge Data-Classes.
025 * 
026 * <BR /><BR />
027 * <EMBED CLASS="external-html" DATA-FILE-ID=JPB_DECLARATION>
028 * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_DIAGRAM>
029 */
030@JDHeaderBackgroundImg
031public abstract class Declaration implements java.io.Serializable
032{
033    /** <EMBED CLASS="external-html" DATA-FILE-ID="SVUID"> */
034    public static final long serialVersionUID = 1;
035
036    /**
037     * For the purposes of passing these around to different parts of the code, every one of
038     * of these are given a unique ID.  This id is unique for a method, whether it was parsed 
039     * from a detail or a summary section.  This id is (probably) not useful outside of the
040     * HTML Processor Classes.
041     * 
042     * <BR /><BR /><B>NOTE:</B> If a subclass of {@code Declaration} is cloned, then this
043     * {@code id} field is also cloned / copied.
044     */
045    public final int id;
046
047    // The id is just created using this counter.
048    private static int idCounter = 1;
049
050    // Helper
051    @SuppressWarnings("rawtypes")
052    static final NodeList EMPTY_NL = new NodeList();
053    // sub-classes use this ==> Package-Visibility
054
055    private String codeHiLiteString = null;
056
057    /**
058     * This returns the {@code String} to send to the Syntax {@link HiLiter}.  This is what is
059     * inserted into the HiLited Code Portion of a Details Entry (Method Details,
060     * Field Declaration, etc...).  
061     * 
062     * <BR /><BR /><B>NOTE:</B> This will overloaded by the class {@code Callable}.
063     * 
064     * <BR /><BR /><UL CLASS=JDUL>
065     * <LI>{@link Method} will override this, and return its {@link Callable#body} field.</LI>
066     * <LI>{@link Constructor} will override this, returning its {@link Callable#body} field.</LI>
067     * <LI>{@link Field} will return the {@link #signature} field, as declared here.</LI>
068     * <LI>{@link EnumConstant} will return the {@link #signature} field, declared here.</LI>
069     * <LI>{@link AnnotationElem} will return the {@link #signature} field, declared here.</LI>
070     * </UL>
071     * 
072     * @return The {@code String} that is ultimately sent to the Syntax HiLiter, and inserted
073     * into a Java Doc page.
074     */
075    String codeHiLiteString() { return codeHiLiteString; }
076
077
078    // ********************************************************************************************
079    // ********************************************************************************************
080    // Basic String Fields
081    // ********************************************************************************************
082    // ********************************************************************************************
083
084
085    /**
086     * The <B>Name</B> of the java {@link Field}, {@link Method}, {@code Constructor},
087     * {@link EnumConstant} or {@link AnnotationElem}.  This will be a <B>simple, standard</B>
088     * 'Java Identifier'.
089     * 
090     * <BR /><BR />Note that the name of a {@code Constructor} (for-example) is always just the
091     * name of the class.
092     * 
093     * <BR /><BR />This field will never be null.
094     */
095    public final String name;
096
097    /**
098     * The complete, declared <B>Signature</B> (as a {@code String}) of the {@link Method},
099     * {@link Field}, {@link Constructor}, {@link EnumConstant} or {@link AnnotationElem}.
100     * 
101     * <BR /><BR />This field would never be null.
102     */
103    public final String signature;
104
105    /**
106     * The <B>Java Doc Comment</B> of this {@link Field}, {@link Method}, {@code Constructor},
107     * {@link EnumConstant} or {@link AnnotationElem} as a {@code String} - if one exists.  The
108     * Java Doc Comment is the one defined directly above the {@code Declaration}.
109     * 
110     * <BR /><BR />If this {@code Field, Method, Constructor} etc... did not have a Java Doc
111     * Comment placed on it, <I>then this field {@code 'jdComment'} will be {@code null}.</I>
112     */
113    public final String jdComment;
114
115    /**
116     * This just stores the type of {@link Entity} this is.  For sub-classes instances of
117     * {@link Declaration} which are {@link Method}, this field will be equal to
118     * {@link Entity#METHOD}.  For instances of the {@link Field} sub-class, this will equal
119     * {@link Entity#FIELD}, and so on and so forth.
120     * 
121     * <BR /><BR />Mostly, this makes code easier to read when used along-side <B>if-statements</B>
122     * or <B>switch-statements</B> than something akin to {@code Declaration.getClass()}
123     * (when retrieving the specific {@code Declaration} sub-class type).
124     * 
125     * <BR /><BR /><B>NOTE:</B> Both this class, and sub-class {@code Callable} are declared
126     * {@code abstract}, and only instances of Method, Field, Constructor, etc... can be
127     * instantiated.
128     */
129    public final Entity entity;
130
131
132    // ********************************************************************************************
133    // ********************************************************************************************
134    // Line Numbers
135    // ********************************************************************************************
136    // ********************************************************************************************
137
138
139    /**
140     * This contains the source-code file line-number where the signature for this
141     * {@code Declaration}.
142     */
143    public final int signatureLineNumber;
144
145    /**
146     * This contains the source-code file line-number for the <B STYLE='color: red'>last</B>
147     * line of the Java Doc Comment, if present.  If there were no JavaDoc Comment, this shall
148     * contain {@code -1}.
149     */
150    public final int jdStartLineNumber;
151
152    /**
153     * This contains the source-code file line-number for the <B STYLE='color: red'>last</B>
154     * line of the Java Doc Comment, if present.  If there were no JavaDoc Comment, this shall
155     * contain {@code -1}.
156     */
157    public final int jdEndLineNumber;
158
159
160    // ********************************************************************************************
161    // ********************************************************************************************
162    // Modifiers: public, private, protected, static, default, final, volatile, ...
163    // ********************************************************************************************
164    // ********************************************************************************************
165
166
167    /**
168     * The {@code 'modifiers'} placed on this {@code Declaration}.  This includes {@code String's}
169     * such as: {@code public, static, final} etc...  Note that this field is kept
170     * {@code protected} so that it cannot be changed, but it's contents can be retrieved with
171     * the getter methods provided, below.
172     * 
173     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_PI_ARR_NOTE>
174     * 
175     * @see #getModifiers()
176     * @see #getModifiers(Consumer)
177     * @see #hasModifier(String)
178     * @see #hasModifiers()
179     * @see #numModifiers()
180     */
181    protected final String[] modifiers;
182
183    /**
184     * Retrieves the list of {@code 'modifiers'} as {@code String}-array.  The {@code modifiers}
185     * are just the words that come before a {@link Field}, {@link Constructor}, {@link Method},
186     * {@link EnumConstant} or {@link AnnotationElem} such as: {@code public, private, protected,
187     * final} - among others.
188     * 
189     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_PI_ARR_NOTE>
190     * 
191     * @return An instance of {@code String[]}, which was built using Java's {@code clone()}
192     * method, <I>thereby protecting its contents.</I>
193     * 
194     * @see #modifiers
195     */
196    public String[] getModifiers()
197    { return modifiers.clone(); }
198
199    /**
200     * Retrieves the list of {@code 'modifiers'}.  User provides an insertion function of their
201     * choice.  The {@code 'modifiers'} are just the words that come before a {@link Field},
202     * {@link Constructor}, {@link Method}, etc... - including (for-example): {@code public,
203     * private, protected, final, volatile} (of which there are quite a few).
204     * 
205     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_PI_ARR_NOTE>
206     * 
207     * @param acceptModifiersAsStringConsumer
208     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_CONSUM DATA-P1=getModifiers DATA-P2=modifiers>
209     * 
210     * @see #modifiers
211     */
212    public void getModifiers(Consumer<String> acceptModifiersAsStringConsumer)
213    { for (String modifier : modifiers) acceptModifiersAsStringConsumer.accept(modifier); }
214
215    /**
216     * The user may pass any of the standard Java Modifiers for Declarations to ask whether this
217     * {@code Declaration} was defined using that {@code modifier}.
218     * 
219     * @param modifier a (lower-case) {@code String} such as: {@code 'public', 'static', 'final'}
220     * etc...
221     * 
222     * @return {@code TRUE} if the provided {@code 'modifier'} is, actually, one of the modifiers
223     * listed within this {@code Declaration's} internal {@code 'modifiers'} array.
224     * 
225     * @see #modifiers
226     */
227    public boolean hasModifier(String modifier)
228    { for (String m : modifiers) if (m.equals(modifier)) return true; return false; }
229
230    /**
231     * Reports whether this instance of {@code Declaration} has any modifiers attached to it.
232     * It simply returns whether or not the internal {@code 'modifiers'} array has length bigger
233     * than zero.
234     * 
235     * @return Returns {@code TRUE} if there were any modifiers - <I>{@code public, static, final}
236     * etc...</I> - that were specified in this declaration.
237     * 
238     * @see #modifiers
239     */
240    public boolean hasModifiers() { return modifiers.length > 0; }
241
242    /**
243     * Returns the number of {@code 'modifiers'} - <I><CODE>public, static, final</CODE> etc...</I>
244     * - that were specified by 'this' {@code Declaration}
245     * 
246     * @return Returns the length of the <CODE>protected</CODE> (internal) {@code 'modifiers'}
247     * array.
248     * 
249     * @see #modifiers
250     */
251    public int numModifiers() { return modifiers.length; }
252
253
254    // ********************************************************************************************
255    // ********************************************************************************************
256    // Annotations (The annotations attached to a declaration)
257    // ********************************************************************************************
258    // ********************************************************************************************
259
260
261    /**
262     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_DECL_ANNOT>
263     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_PI_ARR_NOTE>
264     * 
265     * @see #getAnnotations()
266     * @see #getAnnotations(Consumer)
267     * @see #hasAnnotations()
268     * @see #numAnnotations()
269     */
270    protected final String[] annotations;
271
272    /**
273     * Retrieves the list of {@code 'annotations'} as {@code String}-array.  The
274     * {@code annotations} are the {@code String's} that begin with the {@code '@'} symbol, and
275     * are, occasionally, placed before a {@link Method}, {@link Constructor}, {@link Field},
276     * {@link EnumConstant} or {@link AnnotationElem}.
277     * 
278     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_PI_ARR_NOTE>
279     * 
280     * @return An instance of {@code String[]}, which was built using Java's {@code clone()}
281     * method, <I>thereby protecting its contents.</I>
282     * 
283     * @see #annotations
284     */
285    public String[] getAnnotations()
286    { return annotations.clone(); }
287
288    /**
289     * Retrieves the list of {@code 'annotations'}.  User provides an insertion function of their
290     * choice.
291     * 
292     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_PI_ARR_NOTE>
293     * 
294     * @param acceptAnnotationsAsStringConsumer
295     * <EMBED CLASS='external-html' DATA-FILE-ID=JPB_CONSUM
296     *  DATA-P1=getAnnotations DATA-P2=annotations>
297     * 
298     * @see #annotations
299     */
300    public void getAnnotations(Consumer<String> acceptAnnotationsAsStringConsumer)
301    { for (String annotation : annotations) acceptAnnotationsAsStringConsumer.accept(annotation); }
302
303    /**
304     * Reports whether this instance of {@code Declaration} has any annotations attached to it.
305     * It simply returns whether or not the internal {@code 'modifiers'} array has length bigger
306     * than zero.
307     * 
308     * @return Returns {@code TRUE} if there were any modifiers - <I>{@code public, static, final}
309     * etc...</I> - that were specified in this declaration.
310     * 
311     * @see #annotations
312     */
313    public boolean hasAnnotations() { return annotations.length > 0; }
314
315    /**
316     * This returns the number of {@code 'annotations'} that may or may not have placed on
317     * {@code 'this' Declaration}.  If this {@link Method}, {@link Constructor}, {@code Field},
318     * {@code EnumConstant} or {@code AnnotationElem} was not annotated with anything, then the
319     * return-value of this method is, simply, zero.
320     * 
321     * @return Returns the length of the {@code protected} (internal) {@code 'annotations'} array.
322     * 
323     * @see #annotations
324     */
325    public int numAnnotations() { return annotations.length; }
326
327
328    // ********************************************************************************************
329    // ********************************************************************************************
330    // Constructor for this class Declaration
331    // ********************************************************************************************
332    // ********************************************************************************************
333
334
335    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
336    // Used internally in this package, by subclasses
337    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
338
339    Declaration(
340            NodeList<AnnotationExpr>    annotations,
341            NodeList<Modifier>          modifiers,
342            String                      name,
343            String                      signature,
344            Optional<JavadocComment>    jdComment,
345            Entity                      entity,
346            Optional<Position>          signatureLineNumber
347        )
348    {
349        this.id                 = idCounter++;
350        this.name               = name.trim();
351        this.signature          = signature.trim();
352        this.entity             = entity;
353        this.codeHiLiteString   = signature;
354
355        if ((signatureLineNumber == null) || (! signatureLineNumber.isPresent()))
356            this.signatureLineNumber = -1;
357        else
358        {
359            // This *IS NOT* a *HACK* - BUT....
360            //
361            // This is necessitated since a Declaration can *ALSO* be built from the Java Doc
362            // HTML Signature - which, *RIGHT NOW* always is Line #1 of the String passed to the
363            // Java Parser parseXXX(String sig) method - so if that changed for some odd reason
364            // this would break.  It has a sig.trim() line, so this should never break...
365            //
366            // NOTE: The point is that "Synthetic Methods" and the "Auto-Generated Constructor"
367            //       do not have line numbers because they are not in the source-file
368            //
369            // ALSO: There is no class, interface, enum or any TYPE/CIET where a Declaration would
370            //       be on Line#1 - it is impossible!  JP is returning Line#1 because in
371            //       JavaDocHTMLFile, the HTML-Signature is used to do the parsing for these
372            //       Java "Auto-Generated" entities.
373
374            int line = signatureLineNumber.get().line;
375            this.signatureLineNumber = (line != 1) ? line : -1;
376        }
377
378
379        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
380        // JavaDocHTML passes null to this parameter for AnnotationElem and for EnumConstant.
381        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
382        //
383        // It doesn't build a JavaParser class
384
385        if ((jdComment == null) || (! jdComment.isPresent()))
386        {
387            this.jdComment          = null;
388            this.jdStartLineNumber  = -1;
389            this.jdEndLineNumber    = -1;
390        }
391        else
392        {
393            JavadocComment jdCommentJP = jdComment.get();
394
395            this.jdComment = jdCommentJP.toString();
396
397            Optional<Range> jdRangeOPT = jdCommentJP.getRange();
398
399            // This is "doubt" in the JavaParser stuff.  I cannot see why this would ever return
400            // null, but I don't know JP that well, so I'm leaving this here.  It seems extremely
401            // supefluous to question whether the "Optional" itself might be null.
402
403            Range range = (jdRangeOPT == null)
404                ? null
405                : (jdRangeOPT.isPresent() ? jdRangeOPT.get() : null);
406
407            this.jdStartLineNumber  = (range == null) ? -1 : range.begin.line;
408            this.jdEndLineNumber    = (range == null) ? -1 : range.end.line;
409
410            /*
411            // The Constructor.equals(other) wasn't working, now it is.  Leave this here, since
412            // adding the line-numbers is a relatively new thing.
413
414            if (entity == Entity.CONSTRUCTOR)
415            {
416                System.out.println("jdComment: " + range.toString());
417                System.out.println("jdstart: " + this.jdStartLineNumber + ",\tjdend: " +
418                    this.jdEndLineNumber + ",\tsigLine: " + this.signatureLineNumber);
419            }
420            */
421        }
422
423
424        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
425        // Build String-Arrays
426        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
427
428        this.annotations = (annotations.size() > 0)
429            ? new String[annotations.size()]
430            : StringParse.EMPTY_STR_ARRAY;
431
432        this.modifiers = (modifiers.size() > 0)
433            ? new String[modifiers.size()]
434            : StringParse.EMPTY_STR_ARRAY;
435
436        int i;
437
438
439        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
440        // Fill the String-Arrays
441        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
442
443        i=0;
444        for (AnnotationExpr ae : annotations)
445            this.annotations[i++] =
446                LexicalPreservingPrinter.print(LexicalPreservingPrinter.setup(ae)).trim();
447
448        i=0;
449        for (Modifier m : modifiers) this.modifiers[i++] = m.toString().trim();
450    }
451
452
453    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
454    // private constructor, used by subclasses' clone method
455    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
456
457    Declaration(Declaration other)
458    {
459        this.id                     = other.id;
460        this.annotations            = other.annotations.clone();
461        this.modifiers              = other.modifiers.clone();
462        this.name                   = other.name;
463        this.signature              = other.signature;
464        this.jdComment              = other.jdComment;
465        this.entity                 = other.entity;
466        this.signatureLineNumber    = other.signatureLineNumber;
467        this.jdStartLineNumber      = other.jdStartLineNumber;
468        this.jdEndLineNumber        = other.jdEndLineNumber;
469        this.codeHiLiteString       = other.codeHiLiteString;
470    }
471
472
473    // ********************************************************************************************
474    // ********************************************************************************************
475    // HELPERS - toString(int flags)
476    // ********************************************************************************************
477    // ********************************************************************************************
478
479
480    Torello.Java.Additional.Ret2<Boolean, Boolean> jowFlags(int flags)
481    {
482        boolean onlyJOW         = (flags & JOW_INSTEAD) > 0;
483        boolean addJOW          = (flags & JOW_ALSO) > 0;
484
485        // "onlyJOW" has a higher FLAG-PRECEDENCY
486        if (onlyJOW && addJOW) addJOW = false;
487
488        return new Torello.Java.Additional.Ret2<>(addJOW, onlyJOW);
489    }
490
491    String printedName(String entity, int numSpaces, boolean color)
492    {
493        return
494            StringParse.rightSpacePad(entity + " Name:", numSpaces) +
495            "[" + (color ? BCYAN : "") + name + (color ? RESET : "") + "]\n";
496    }
497
498    String printedSignature(int numSpaces, boolean color)
499    {
500        return
501            StringParse.rightSpacePad("Signature:", numSpaces) +
502            "[" + (color ? BYELLOW : "") +
503            StringParse.abbrevEndRDSF(signature, MAX_STR_LEN, true) +
504            (color ? RESET : "") + "]\n";
505    }
506
507    String printedDeclaration(int numSpaces, boolean color)
508    {
509        return
510            StringParse.rightSpacePad("Declaration:", numSpaces) +
511            "[" + (color ? BYELLOW : "") +
512            StringParse.abbrevEndRDSF(signature, MAX_STR_LEN, true) +
513            (color ? RESET : "") + "]\n";
514    }
515
516    String printedModifiers(int numSpaces)
517    {
518        return
519            StringParse.rightSpacePad("Modifiers:", numSpaces) +
520            "[" + StrCSV.toCSV(modifiers, true, true, null) + "]\n";
521    }
522
523    String printedLineNumbers(int numSpaces, boolean color)
524    {
525        return 
526            StringParse.rightSpacePad("Line Numbers:", numSpaces) + "[" +
527
528            "sig=" +        (color ? BRED : "") + signatureLineNumber +
529                (color ? RESET : "") + ", " +
530            "jdStart=" +    (color ? BRED : "") + jdStartLineNumber +
531                (color ? RESET : "") + ", " +
532            "jdEnd=" +      (color ? BRED : "") + jdEndLineNumber +
533                (color ? RESET : "") + "]";
534    }
535
536    String printedComments(int numSpaces, boolean color, boolean comments)
537    {
538        if (! comments)
539            return "";
540
541        else if (jdComment == null) return
542            "\n" +
543            StringParse.rightSpacePad("JD Comments:", numSpaces) +
544            (color ? BRED : "") + "None Available / Not Included" + (color ? RESET : "");
545
546        else return
547            "\n" +
548            StringParse.rightSpacePad("JD Comments:", numSpaces) +
549            "[" +
550            (color ? BGREEN : "") +
551            StringParse.abbrevEndRDSF(jdComment, MAX_STR_LEN, true) +
552            (color ? RESET : "") +
553            "]";
554    }
555
556    /**
557     * Dummy Method.  Overriden by Concrete Sub-Classes.
558     * @see Method#toString()
559     * @see Field#toString()
560     * @see Constructor#toString()
561     */
562    public String toString()
563    { return "Declaration is Abstract, all Concrete Sub-Classes Override this method."; }
564
565    /**
566     * Dummy Method.  Overriden by Concrete Sub-Classes.
567     * @see Method#toString(int)
568     * @see Field#toString(int)
569     * @see Constructor#toString(int)
570     */
571    public String toString(int flags)
572    { return "Declaration is Abstract, all Concrete Sub-Classes Override this method."; }
573
574
575    // ********************************************************************************************
576    // ********************************************************************************************
577    // HELPERS
578    // ********************************************************************************************
579    // ********************************************************************************************
580
581
582    /**
583     * This removes the package information from a {@code String[]} array of class-names or
584     * interface-names.
585     * 
586     * @param types This input {@code String[]} array should be a list of the input-parameter
587     * 'types' to a {@code method} or {@code constructor}.  It may be any list of class/interface
588     * {@code String's}.
589     * 
590     * @return This will return a parallel array that contains the same classes or interfaces, but
591     * any of the input {@code String's} that contained full-package names will have had the
592     * package part of the name stripped out.  For parameter-types in the original array that did
593     * not have package-information the original {@code String} will be returned.
594     */
595    static String[] REMOVE_PACKAGE_INFORMATION(String[] types)
596    {
597        String[] ret = new String[types.length];
598
599        for (int i=0; i < types.length; i++)
600
601            ret[i] = types[i].contains(".")
602                ? StringParse.PACKAGE_NAME.matcher(types[i]).replaceAll("")
603                : types[i];
604
605        return ret;
606    }
607
608    /**
609     * This removes the package information from a single class or interface name.
610     *
611     * @param type This lone input {@code String}-parameter should contain a single class or
612     * interface name that includes the full package name.
613     *
614     * @return This will return a single {@code String} that is identical to the input-type, but
615     * has had the full package-name removed - if it had full-package details in the first place.
616     * If there were no package-name {@code String's} as substrings of the input, the original
617     * {@code String} will be returned.
618     */
619    static String REMOVE_PACKAGE_INFORMATION(String type)
620    { return type.contains(".") ? StringParse.PACKAGE_NAME.matcher(type).replaceAll("") : type; }
621}