001/*
002 * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclets.toolkit.util;
027
028import com.sun.source.doctree.SerialFieldTree;
029
030import javax.lang.model.element.Element;
031import javax.lang.model.element.ExecutableElement;
032import javax.lang.model.element.ModuleElement;
033import javax.lang.model.element.PackageElement;
034import javax.lang.model.element.TypeElement;
035import javax.lang.model.element.VariableElement;
036import javax.lang.model.type.ArrayType;
037import javax.lang.model.type.PrimitiveType;
038import javax.lang.model.type.TypeMirror;
039import javax.lang.model.util.SimpleElementVisitor14;
040import javax.lang.model.util.SimpleTypeVisitor9;
041import java.util.Comparator;
042import java.util.List;
043
044/**
045 * A collection of {@code Comparator} factory methods.
046 */
047public class Comparators {
048
049    private final Utils utils;
050
051    Comparators(Utils utils) {
052        this.utils = utils;
053    }
054
055    private Comparator<Element> moduleComparator = null;
056
057    /**
058     * Comparator for ModuleElements, simply compares the fully qualified names
059     * @return a Comparator
060     */
061    public Comparator<Element> makeModuleComparator() {
062        if (moduleComparator == null) {
063            moduleComparator = new ElementComparator() {
064                @Override
065                public int compare(Element mod1, Element mod2) {
066                    return compareFullyQualifiedNames(mod1, mod2);
067                }
068            };
069        }
070        return moduleComparator;
071    }
072
073    private Comparator<Element> allClassesComparator = null;
074
075    /**
076     * Returns a Comparator for all classes, compares the simple names of
077     * TypeElement, if equal then the fully qualified names, and if equal again
078     * the names of the enclosing modules.
079     *
080     * @return Comparator
081     */
082    public Comparator<Element> makeAllClassesComparator() {
083        if (allClassesComparator == null) {
084            allClassesComparator = new ElementComparator() {
085                @Override
086                public int compare(Element e1, Element e2) {
087                    int result = compareNames(e1, e2);
088                    if (result == 0)
089                        result = compareFullyQualifiedNames(e1, e2);
090                    if (result == 0)
091                        result = compareModuleNames(e1, e2);
092                    return result;
093                }
094            };
095        }
096        return allClassesComparator;
097    }
098
099    private Comparator<Element> packageComparator = null;
100
101    /**
102     * Returns a Comparator for packages, by comparing the fully qualified names,
103     * and if those are equal the names of the enclosing modules.
104     *
105     * @return a Comparator
106     */
107    public Comparator<Element> makePackageComparator() {
108        if (packageComparator == null) {
109            packageComparator = new ElementComparator() {
110                @Override
111                public int compare(Element pkg1, Element pkg2) {
112                    int result = compareFullyQualifiedNames(pkg1, pkg2);
113                    if (result == 0)
114                        result = compareModuleNames(pkg1, pkg2);
115                    return result;
116                }
117            };
118        }
119        return packageComparator;
120    }
121
122    private Comparator<Element> summaryComparator = null;
123
124    /**
125     * Returns a Comparator for items listed on summary list pages
126     * (like deprecated or preview summary pages), by comparing the
127     * fully qualified names, and if those are equal the names of the enclosing modules.
128     *
129     * @return a Comparator
130     */
131    public Comparator<Element> makeSummaryComparator() {
132        if (summaryComparator == null) {
133            summaryComparator = new ElementComparator() {
134                @Override
135                public int compare(Element e1, Element e2) {
136                    int result = compareFullyQualifiedNames(e1, e2);
137                    if (result != 0) {
138                        return result;
139                    }
140                    // if elements are executable compare their parameter arrays
141                    result = compareParameters(e1, e2);
142                    if (result != 0) {
143                        return result;
144                    }
145                    return compareModuleNames(e1, e2);
146                }
147            };
148        }
149        return summaryComparator;
150    }
151
152    private Comparator<SerialFieldTree> serialFieldTreeComparator = null;
153
154    /**
155     * Returns a Comparator for SerialFieldTree.
156     * @return a Comparator
157     */
158    public Comparator<SerialFieldTree> makeSerialFieldTreeComparator() {
159        if (serialFieldTreeComparator == null) {
160            serialFieldTreeComparator
161                = (SerialFieldTree o1, SerialFieldTree o2) -> {
162                    String s1 = o1.getName().toString();
163                    String s2 = o2.getName().toString();
164                    return s1.compareTo(s2);
165                };
166        }
167        return serialFieldTreeComparator;
168    }
169
170    /**
171     * Returns a general purpose comparator.
172     * @return a Comparator
173     */
174    public Comparator<Element> makeGeneralPurposeComparator() {
175        return makeClassUseComparator();
176    }
177
178    private Comparator<Element> overrideUseComparator = null;
179
180    /**
181     * Returns a Comparator for overrides and implements,
182     * used primarily on methods, compares the name first,
183     * then compares the simple names of the enclosing
184     * TypeElement and the fully qualified name of the enclosing TypeElement.
185     * @return a Comparator
186     */
187    public Comparator<Element> makeOverrideUseComparator() {
188        if (overrideUseComparator == null) {
189            overrideUseComparator = new ElementComparator() {
190                @Override
191                public int compare(Element o1, Element o2) {
192                    int result = utils.compareStrings(utils.getSimpleName(o1),
193                        utils.getSimpleName(o2));
194                    if (result != 0) {
195                        return result;
196                    }
197                    if (!utils.isTypeElement(o1) && !utils.isTypeElement(o2)
198                        && !utils.isPackage(o1) && !utils.isPackage(o2)) {
199                        TypeElement t1 = utils.getEnclosingTypeElement(o1);
200                        TypeElement t2 = utils.getEnclosingTypeElement(o2);
201                        result = utils.compareStrings(utils.getSimpleName(t1),
202                            utils.getSimpleName(t2));
203                        if (result != 0)
204                            return result;
205                    }
206                    result
207                        = utils.compareStrings(utils.getFullyQualifiedName(o1),
208                            utils.getFullyQualifiedName(o2));
209                    if (result != 0)
210                        return result;
211                    return compareElementKinds(o1, o2);
212                }
213            };
214        }
215        return overrideUseComparator;
216    }
217
218    private Comparator<Element> indexUseComparator = null;
219
220    /**
221     * Returns an {@code Element} Comparator for index file presentations, and are sorted as follows.
222     * If comparing modules and/or packages then simply compare the qualified names,
223     * if comparing a module or a package with a type/member then compare the
224     * FullyQualifiedName of the module or a package with the SimpleName of the entity,
225     * otherwise:
226     * 1. compare the ElementKind ex: Module, Package, Interface etc.
227     * 2a. if equal and if the type is of ExecutableElement(Constructor, Methods),
228     *     a case insensitive comparison of parameter the type signatures
229     * 2b. if equal, case sensitive comparison of the type signatures
230     * 3. if equal, compare the FQNs of the entities
231     * 4. finally, if equal, compare the names of the enclosing modules
232     * @return an element comparator for index file use
233     */
234    public Comparator<Element> makeIndexElementComparator() {
235        if (indexUseComparator == null) {
236            indexUseComparator = new ElementComparator() {
237                /**
238                 * Compares two elements.
239                 *
240                 * @param e1 - an element.
241                 * @param e2 - an element.
242                 * @return a negative integer, zero, or a positive integer as the first
243                 * argument is less than, equal to, or greater than the second.
244                 */
245                @Override
246                public int compare(Element e1, Element e2) {
247                    // first, compare names as appropriate
248                    int result = utils.compareStrings(getIndexElementKey(e1),
249                        getIndexElementKey(e2));
250                    if (result != 0) {
251                        return result;
252                    }
253                    // if names are the same, compare element kinds
254                    result = compareElementKinds(e1, e2);
255                    if (result != 0) {
256                        return result;
257                    }
258                    // if element kinds are the same, and are executable,
259                    // compare the parameter arrays
260                    result = compareParameters(e1, e2);
261                    if (result != 0) {
262                        return result;
263                    }
264                    // else fall back on fully qualified names
265                    result = compareFullyQualifiedNames(e1, e2);
266                    if (result != 0)
267                        return result;
268                    return compareModuleNames(e1, e2);
269                }
270            };
271        }
272        return indexUseComparator;
273    }
274
275    /**
276     * {@return the element's primary key for use in the index comparator}
277     * This method can be used by other comparators which need to produce results
278     * that are consistent with the index comparator.
279     *
280     * @param element an element
281     */
282    public String getIndexElementKey(Element element) {
283        return switch (element.getKind()) {
284        case MODULE, PACKAGE -> utils.getFullyQualifiedName(element);
285        default -> utils.getSimpleName(element);
286        };
287    }
288
289    private Comparator<TypeMirror> typeMirrorClassUseComparator = null;
290
291    /**
292     * Returns a comparator that compares the fully qualified names of two type mirrors.
293     *
294     * @return the comparator
295     */
296    public Comparator<TypeMirror> makeTypeMirrorClassUseComparator() {
297        if (typeMirrorClassUseComparator == null) {
298            typeMirrorClassUseComparator
299                = (TypeMirror type1, TypeMirror type2) -> {
300                    String s1 = utils.getQualifiedTypeName(type1);
301                    String s2 = utils.getQualifiedTypeName(type2);
302                    return utils.compareStrings(s1, s2);
303                };
304        }
305        return typeMirrorClassUseComparator;
306    }
307
308    private Comparator<TypeMirror> typeMirrorIndexUseComparator = null;
309
310    /**
311     * Returns a comparator that compares the simple names of two type mirrors,
312     * or the fully qualified names if the simple names are equal.
313     *
314     * @return the comparator
315     */
316    public Comparator<TypeMirror> makeTypeMirrorIndexUseComparator() {
317        if (typeMirrorIndexUseComparator == null) {
318            typeMirrorIndexUseComparator = (TypeMirror t1, TypeMirror t2) -> {
319                int result = utils.compareStrings(utils.getTypeName(t1, false),
320                    utils.getTypeName(t2, false));
321                if (result != 0)
322                    return result;
323                return utils.compareStrings(utils.getQualifiedTypeName(t1),
324                    utils.getQualifiedTypeName(t2));
325            };
326        }
327        return typeMirrorIndexUseComparator;
328    }
329
330    private Comparator<Element> classUseComparator = null;
331
332    /**
333     * Comparator for ClassUse presentations, and sorts as follows:
334     * 1. member names
335     * 2. then fully qualified member names
336     * 3. then parameter types if applicable
337     * 4. the element kinds ie. package, class, interface etc.
338     * 5. finally the name of the enclosing modules
339     * @return a comparator to sort classes and members for class use
340     */
341    public Comparator<Element> makeClassUseComparator() {
342        if (classUseComparator == null) {
343            classUseComparator = new ElementComparator() {
344                /**
345                 * Compares two Elements.
346                 *
347                 * @param e1 - an element.
348                 * @param e2 - an element.
349                 * @return a negative integer, zero, or a positive integer as the first
350                 * argument is less than, equal to, or greater than the second.
351                 */
352                @Override
353                public int compare(Element e1, Element e2) {
354                    int result = compareNames(e1, e2);
355                    if (result != 0) {
356                        return result;
357                    }
358                    result = compareFullyQualifiedNames(e1, e2);
359                    if (result != 0) {
360                        return result;
361                    }
362                    result = compareParameters(e1, e2);
363                    if (result != 0) {
364                        return result;
365                    }
366                    result = compareElementKinds(e1, e2);
367                    if (result != 0) {
368                        return result;
369                    }
370                    return compareModuleNames(e1, e2);
371                }
372            };
373        }
374        return classUseComparator;
375    }
376
377    /**
378     * A general purpose comparator to sort Element entities, basically provides the building blocks
379     * for creating specific comparators for an use-case.
380     */
381    private abstract class ElementComparator implements Comparator<Element> {
382        public ElementComparator() {
383        }
384
385        /**
386         * compares two parameter arrays by first comparing the length of the arrays, and
387         * then each Type of the parameter in the array.
388         * @param params1 the first parameter array.
389         * @param params2 the first parameter array.
390         * @return a negative integer, zero, or a positive integer as the first
391         *         argument is less than, equal to, or greater than the second.
392         */
393        protected int compareParameters(boolean caseSensitive,
394                List<? extends VariableElement> params1,
395                List<? extends VariableElement> params2) {
396
397            return utils.compareStrings(caseSensitive,
398                getParametersAsString(params1),
399                getParametersAsString(params2));
400        }
401
402        String getParametersAsString(List<? extends VariableElement> params) {
403            StringBuilder sb = new StringBuilder();
404            for (VariableElement param : params) {
405                TypeMirror t = param.asType();
406                // prefix P for primitive and R for reference types, thus items
407                // will
408                // be ordered lexically and correctly.
409                sb.append(getTypeCode(t)).append("-").append(t).append("-");
410            }
411            return sb.toString();
412        }
413
414        private String getTypeCode(TypeMirror t) {
415            return new SimpleTypeVisitor9<String, Void>() {
416
417                @Override
418                public String visitPrimitive(PrimitiveType t, Void p) {
419                    return "P";
420                }
421
422                @Override
423                public String visitArray(ArrayType t, Void p) {
424                    return visit(t.getComponentType());
425                }
426
427                @Override
428                protected String defaultAction(TypeMirror e, Void p) {
429                    return "R";
430                }
431
432            }.visit(t);
433        }
434
435        /**
436         * Compares two Elements, typically the name of a method,
437         * field or constructor.
438         * @param e1 the first Element.
439         * @param e2 the second Element.
440         * @return a negative integer, zero, or a positive integer as the first
441         *         argument is less than, equal to, or greater than the second.
442         */
443        protected int compareNames(Element e1, Element e2) {
444            return utils.compareStrings(utils.getSimpleName(e1),
445                utils.getSimpleName(e2));
446        }
447
448        /**
449         * Compares the fully qualified names of the entities
450         * @param e1 the first Element.
451         * @param e2 the first Element.
452         * @return a negative integer, zero, or a positive integer as the first
453         *         argument is less than, equal to, or greater than the second.
454         */
455        protected int compareFullyQualifiedNames(Element e1, Element e2) {
456            // add simple name to be compatible
457            String thisElement = getFullyQualifiedName(e1);
458            String thatElement = getFullyQualifiedName(e2);
459            return utils.compareStrings(thisElement, thatElement);
460        }
461
462        /**
463         * Compares the name of the modules of two elements.
464         * @param e1 the first element
465         * @param e2 the second element
466         * @return a negative integer, zero, or a positive integer as the first
467         *         argument is less than, equal to, or greater than the second
468         */
469        protected int compareModuleNames(Element e1, Element e2) {
470            ModuleElement m1 = utils.elementUtils.getModuleOf(e1);
471            ModuleElement m2 = utils.elementUtils.getModuleOf(e2);
472            if (m1 != null && m2 != null) {
473                return compareFullyQualifiedNames(m1, m2);
474            } else if (m1 != null) {
475                return 1;
476            } else if (m2 != null) {
477                return -1;
478            }
479            return 0;
480        }
481
482        /**
483         * Compares the parameter arrays of two elements if they both are executable.
484         * @param e1 the first element
485         * @param e2 the second element
486         * @return a negative integer, zero, or a positive integer as the first
487         *         argument is less than, equal to, or greater than the second
488         */
489        protected int compareParameters(Element e1, Element e2) {
490            int result = 0;
491            if (hasParameters(e1) && hasParameters(e2)) {
492                List<? extends VariableElement> parameters1
493                    = ((ExecutableElement) e1).getParameters();
494                List<? extends VariableElement> parameters2
495                    = ((ExecutableElement) e2).getParameters();
496                result = compareParameters(false, parameters1, parameters2);
497                if (result != 0) {
498                    return result;
499                }
500                result = compareParameters(true, parameters1, parameters2);
501            }
502            return result;
503        }
504
505        /**
506         * Compares the kinds of two elements.
507         * @param e1 the first element
508         * @param e2 the second element
509         * @return a negative integer, zero, or a positive integer as the first
510         *         argument is less than, equal to, or greater than the second
511         */
512        protected int compareElementKinds(Element e1, Element e2) {
513            return Integer.compare(getKindIndex(e1), getKindIndex(e2));
514        }
515
516        private int getKindIndex(Element e) {
517            return switch (e.getKind()) {
518            case MODULE -> 0;
519            case PACKAGE -> 1;
520            case CLASS -> 2;
521            case ENUM -> 3;
522            case ENUM_CONSTANT -> 4;
523            case RECORD -> 5;
524            case INTERFACE -> 6;
525            case ANNOTATION_TYPE -> 7;
526            case FIELD -> 8;
527            case CONSTRUCTOR -> 9;
528            case METHOD -> 10;
529            default -> throw new IllegalArgumentException(
530                e.getKind().toString());
531            };
532        }
533
534        boolean hasParameters(Element e) {
535            return new SimpleElementVisitor14<Boolean, Void>() {
536                @Override
537                public Boolean visitExecutable(ExecutableElement e, Void p) {
538                    return true;
539                }
540
541                @Override
542                protected Boolean defaultAction(Element e, Void p) {
543                    return false;
544                }
545
546            }.visit(e);
547        }
548
549        /**
550         * The fully qualified names of the entities, used solely by the comparator.
551         *
552         * @return a negative integer, zero, or a positive integer as the first argument is less
553         * than, equal to, or greater than the second.
554         */
555        private String getFullyQualifiedName(Element e) {
556            return new SimpleElementVisitor14<String, Void>() {
557                @Override
558                public String visitModule(ModuleElement e, Void p) {
559                    return e.getQualifiedName().toString();
560                }
561
562                @Override
563                public String visitPackage(PackageElement e, Void p) {
564                    return e.getQualifiedName().toString();
565                }
566
567                @Override
568                public String visitExecutable(ExecutableElement e, Void p) {
569                    // For backward compatibility
570                    return getFullyQualifiedName(e.getEnclosingElement())
571                        + "." + e.getSimpleName().toString();
572                }
573
574                @Override
575                public String visitType(TypeElement e, Void p) {
576                    return e.getQualifiedName().toString();
577                }
578
579                @Override
580                protected String defaultAction(Element e, Void p) {
581                    return utils.getEnclosingTypeElement(e).getQualifiedName()
582                        .toString()
583                        + "." + e.getSimpleName().toString();
584                }
585            }.visit(e);
586        }
587    }
588}