1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.javadoc.internal.doclets.toolkit.util;
  27 
  28 import com.sun.source.doctree.SerialFieldTree;
  29 import jdk.javadoc.internal.doclets.formats.html.SearchIndexItem;
  30 
  31 import javax.lang.model.element.Element;
  32 import javax.lang.model.element.ExecutableElement;
  33 import javax.lang.model.element.ModuleElement;
  34 import javax.lang.model.element.PackageElement;
  35 import javax.lang.model.element.TypeElement;
  36 import javax.lang.model.element.VariableElement;
  37 import javax.lang.model.type.ArrayType;
  38 import javax.lang.model.type.PrimitiveType;
  39 import javax.lang.model.type.TypeMirror;
  40 import javax.lang.model.util.SimpleElementVisitor14;
  41 import javax.lang.model.util.SimpleTypeVisitor9;
  42 import java.util.Comparator;
  43 import java.util.List;
  44 
  45 /**
  46  *  A collection of {@code Comparator} factory methods.
  47  *
  48  *  <p><b>This is NOT part of any supported API.
  49  *  If you write code that depends on this, you do so at your own risk.
  50  *  This code and its internal interfaces are subject to change or
  51  *  deletion without notice.</b>
  52  */
  53 public class Comparators {
  54 
  55     private final Utils utils;
  56 
  57     Comparators(Utils utils) {
  58         this.utils = utils;
  59     }
  60 
  61     private Comparator<Element> moduleComparator = null;
  62 
  63     /**
  64      * Comparator for ModuleElements, simply compares the fully qualified names
  65      * @return a Comparator
  66      */
  67     public Comparator<Element> makeModuleComparator() {
  68         if (moduleComparator == null) {
  69             moduleComparator = new ElementComparator() {
  70                 @Override
  71                 public int compare(Element mod1, Element mod2) {
  72                     return compareFullyQualifiedNames(mod1, mod2);
  73                 }
  74             };
  75         }
  76         return moduleComparator;
  77     }
  78 
  79     private Comparator<Element> allClassesComparator = null;
  80 
  81     /**
  82      * Returns a Comparator for all classes, compares the simple names of
  83      * TypeElement, if equal then the fully qualified names.
  84      *
  85      * @return Comparator
  86      */
  87     public Comparator<Element> makeAllClassesComparator() {
  88         if (allClassesComparator == null) {
  89             allClassesComparator = new ElementComparator() {
  90                 @Override
  91                 public int compare(Element e1, Element e2) {
  92                     int result = compareNames(e1, e2);
  93                     if (result == 0)
  94                         result = compareFullyQualifiedNames(e1, e2);
  95 
  96                     return result;
  97                 }
  98             };
  99         }
 100         return allClassesComparator;
 101     }
 102 
 103     private Comparator<Element> packageComparator = null;
 104 
 105     /**
 106      * Returns a Comparator for packages, by comparing the fully qualified names.
 107      *
 108      * @return a Comparator
 109      */
 110     public Comparator<Element> makePackageComparator() {
 111         if (packageComparator == null) {
 112             packageComparator = new ElementComparator() {
 113                 @Override
 114                 public int compare(Element pkg1, Element pkg2) {
 115                     return compareFullyQualifiedNames(pkg1, pkg2);
 116                 }
 117             };
 118         }
 119         return packageComparator;
 120     }
 121 
 122     private Comparator<Element> deprecatedComparator = null;
 123 
 124     /**
 125      * Returns a Comparator for deprecated items listed on deprecated list page, by comparing the
 126      * fully qualified names.
 127      *
 128      * @return a Comparator
 129      */
 130     public Comparator<Element> makeDeprecatedComparator() {
 131         if (deprecatedComparator == null) {
 132             deprecatedComparator = new ElementComparator() {
 133                 @Override
 134                 public int compare(Element e1, Element e2) {
 135                     return compareFullyQualifiedNames(e1, e2);
 136                 }
 137             };
 138         }
 139         return deprecatedComparator;
 140     }
 141 
 142     private Comparator<SerialFieldTree> serialFieldTreeComparator = null;
 143 
 144     /**
 145      * Returns a Comparator for SerialFieldTree.
 146      * @return a Comparator
 147      */
 148     public Comparator<SerialFieldTree> makeSerialFieldTreeComparator() {
 149         if (serialFieldTreeComparator == null) {
 150             serialFieldTreeComparator = (SerialFieldTree o1, SerialFieldTree o2) -> {
 151                 String s1 = o1.getName().toString();
 152                 String s2 = o2.getName().toString();
 153                 return s1.compareTo(s2);
 154             };
 155         }
 156         return serialFieldTreeComparator;
 157     }
 158 
 159     /**
 160      * Returns a general purpose comparator.
 161      * @return a Comparator
 162      */
 163     public Comparator<Element> makeGeneralPurposeComparator() {
 164         return makeClassUseComparator();
 165     }
 166 
 167     private Comparator<Element> overrideUseComparator = null;
 168 
 169     /**
 170      * Returns a Comparator for overrides and implements,
 171      * used primarily on methods, compares the name first,
 172      * then compares the simple names of the enclosing
 173      * TypeElement and the fully qualified name of the enclosing TypeElement.
 174      * @return a Comparator
 175      */
 176     public Comparator<Element> makeOverrideUseComparator() {
 177         if (overrideUseComparator == null) {
 178             overrideUseComparator = new ElementComparator() {
 179                 @Override
 180                 public int compare(Element o1, Element o2) {
 181                     int result = utils.compareStrings(utils.getSimpleName(o1), utils.getSimpleName(o2));
 182                     if (result != 0) {
 183                         return result;
 184                     }
 185                     if (!utils.isTypeElement(o1) && !utils.isTypeElement(o2) && !utils.isPackage(o1) && !utils.isPackage(o2)) {
 186                         TypeElement t1 = utils.getEnclosingTypeElement(o1);
 187                         TypeElement t2 = utils.getEnclosingTypeElement(o2);
 188                         result = utils.compareStrings(utils.getSimpleName(t1), utils.getSimpleName(t2));
 189                         if (result != 0)
 190                             return result;
 191                     }
 192                     result = utils.compareStrings(utils.getFullyQualifiedName(o1), utils.getFullyQualifiedName(o2));
 193                     if (result != 0)
 194                         return result;
 195                     return compareElementKinds(o1, o2);
 196                 }
 197             };
 198         }
 199         return overrideUseComparator;
 200     }
 201 
 202     private Comparator<Element> indexUseComparator = null;
 203 
 204     /**
 205      *  Returns an {@code Element} Comparator for index file presentations, and are sorted as follows.
 206      *  If comparing modules and/or packages then simply compare the qualified names,
 207      *  if comparing a module or a package with a type/member then compare the
 208      *  FullyQualifiedName of the module or a package with the SimpleName of the entity,
 209      *  otherwise:
 210      *  1. compare the ElementKind ex: Module, Package, Interface etc.
 211      *  2a. if equal and if the type is of ExecutableElement(Constructor, Methods),
 212      *      a case insensitive comparison of parameter the type signatures
 213      *  2b. if equal, case sensitive comparison of the type signatures
 214      *  3. finally, if equal, compare the FQNs of the entities
 215      * @return an element comparator for index file use
 216      */
 217     public Comparator<Element> makeIndexElementComparator() {
 218         if (indexUseComparator == null) {
 219             indexUseComparator = new ElementComparator() {
 220                 /**
 221                  * Compares two elements.
 222                  *
 223                  * @param e1 - an element.
 224                  * @param e2 - an element.
 225                  * @return a negative integer, zero, or a positive integer as the first
 226                  * argument is less than, equal to, or greater than the second.
 227                  */
 228                 @Override
 229                 public int compare(Element e1, Element e2) {
 230                     int result;
 231                     // first, compare names as appropriate
 232                     if ((utils.isModule(e1) || utils.isPackage(e1)) && (utils.isModule(e2) || utils.isPackage(e2))) {
 233                         result = compareFullyQualifiedNames(e1, e2);
 234                     } else if (utils.isModule(e1) || utils.isPackage(e1)) {
 235                         result = utils.compareStrings(utils.getFullyQualifiedName(e1), utils.getSimpleName(e2));
 236                     } else if (utils.isModule(e2) || utils.isPackage(e2)) {
 237                         result = utils.compareStrings(utils.getSimpleName(e1), utils.getFullyQualifiedName(e2));
 238                     } else {
 239                         result = compareNames(e1, e2);
 240                     }
 241                     if (result != 0) {
 242                         return result;
 243                     }
 244                     // if names are the same, compare element kinds
 245                     result = compareElementKinds(e1, e2);
 246                     if (result != 0) {
 247                         return result;
 248                     }
 249                     // if element kinds are the same, and are methods,
 250                     // compare the method parameters
 251                     if (hasParameters(e1)) {
 252                         List<? extends VariableElement> parameters1 = ((ExecutableElement)e1).getParameters();
 253                         List<? extends VariableElement> parameters2 = ((ExecutableElement)e2).getParameters();
 254                         result = compareParameters(false, parameters1, parameters2);
 255                         if (result != 0) {
 256                             return result;
 257                         }
 258                         result = compareParameters(true, parameters1, parameters2);
 259                         if (result != 0) {
 260                             return result;
 261                         }
 262                     }
 263                     // else fall back on fully qualified names
 264                     return compareFullyQualifiedNames(e1, e2);
 265                 }
 266             };
 267         }
 268         return indexUseComparator;
 269     }
 270 
 271     /**
 272      * Returns a comparator for the {@code IndexItem}s in the index page. This is a composite
 273      * comparator that must be able to compare all kinds {@code Element}s as well as
 274      * {@code SearchIndexItem}s.
 275      *
 276      * @return a comparator for index page items.
 277      */
 278     public Comparator<IndexItem> makeIndexComparator(boolean classesOnly) {
 279         Comparator<Element> elementComparator = classesOnly
 280                 ? makeAllClassesComparator()
 281                 : makeIndexElementComparator();
 282         Comparator<SearchIndexItem> searchTagComparator =
 283                 makeGenericSearchIndexComparator();
 284 
 285         return (o1, o2) -> {
 286             // Compare two elements
 287             if (o1.getElement() != null && o2.getElement() != null) {
 288                 return elementComparator.compare(o1.getElement(), o2.getElement());
 289             }
 290             // Compare two search tags
 291             if (o1.getSearchTag() != null && o2.getSearchTag() != null) {
 292                 return searchTagComparator.compare(o1.getSearchTag(), o2.getSearchTag());
 293             }
 294             // Compare an element with a search tag.
 295             // Compares labels, if those are equal put the search tag first.
 296             int d = utils.compareStrings(o1.getLabel(), o2.getLabel());
 297             if (d == 0) {
 298                 d = o1.getElement() == null ? 1 : -1;
 299             }
 300             return d;
 301         };
 302     }
 303 
 304     private Comparator<TypeMirror> typeMirrorClassUseComparator = null;
 305 
 306     /**
 307      * Compares the FullyQualifiedNames of two TypeMirrors
 308      * @return
 309      */
 310     public Comparator<TypeMirror> makeTypeMirrorClassUseComparator() {
 311         if (typeMirrorClassUseComparator == null) {
 312             typeMirrorClassUseComparator = (TypeMirror type1, TypeMirror type2) -> {
 313                 String s1 = utils.getQualifiedTypeName(type1);
 314                 String s2 = utils.getQualifiedTypeName(type2);
 315                 return utils.compareStrings(s1, s2);
 316             };
 317         }
 318         return typeMirrorClassUseComparator;
 319     }
 320 
 321     private Comparator<TypeMirror> typeMirrorIndexUseComparator = null;
 322 
 323     /**
 324      * Compares the SimpleNames of TypeMirrors if equal then the
 325      * FullyQualifiedNames of TypeMirrors.
 326      *
 327      * @return
 328      */
 329     public Comparator<TypeMirror> makeTypeMirrorIndexUseComparator() {
 330         if (typeMirrorIndexUseComparator == null) {
 331             typeMirrorIndexUseComparator = (TypeMirror t1, TypeMirror t2) -> {
 332                 int result = utils.compareStrings(utils.getTypeName(t1, false), utils.getTypeName(t2, false));
 333                 if (result != 0)
 334                     return result;
 335                 return utils.compareStrings(utils.getQualifiedTypeName(t1), utils.getQualifiedTypeName(t2));
 336             };
 337         }
 338         return typeMirrorIndexUseComparator;
 339     }
 340 
 341     private Comparator<Element> classUseComparator = null;
 342 
 343     /**
 344      * Comparator for ClassUse presentations, and sorts as follows:
 345      * 1. member names
 346      * 2. then fully qualified member names
 347      * 3. then parameter types if applicable
 348      * 4. finally the element kinds ie. package, class, interface etc.
 349      * @return a comparator to sort classes and members for class use
 350      */
 351     public Comparator<Element> makeClassUseComparator() {
 352         if (classUseComparator == null) {
 353             classUseComparator = new ElementComparator() {
 354                 /**
 355                  * Compares two Elements.
 356                  *
 357                  * @param e1 - an element.
 358                  * @param e2 - an element.
 359                  * @return a negative integer, zero, or a positive integer as the first
 360                  * argument is less than, equal to, or greater than the second.
 361                  */
 362                 @Override
 363                 public int compare(Element e1, Element e2) {
 364                     int result = compareNames(e1, e2);
 365                     if (result != 0) {
 366                         return result;
 367                     }
 368                     result = compareFullyQualifiedNames(e1, e2);
 369                     if (result != 0) {
 370                         return result;
 371                     }
 372                     if (hasParameters(e1) && hasParameters(e2)) {
 373                         List<? extends VariableElement> parameters1 = ((ExecutableElement)e1).getParameters();
 374                         List<? extends VariableElement> parameters2 = ((ExecutableElement)e2).getParameters();
 375                         result = compareParameters(false, parameters1, parameters2);
 376                         if (result != 0) {
 377                             return result;
 378                         }
 379                         result = compareParameters(true, parameters1, parameters2);
 380                     }
 381                     if (result != 0) {
 382                         return result;
 383                     }
 384                     return compareElementKinds(e1, e2);
 385                 }
 386             };
 387         }
 388         return classUseComparator;
 389     }
 390 
 391     /**
 392      * A general purpose comparator to sort Element entities, basically provides the building blocks
 393      * for creating specific comparators for an use-case.
 394      */
 395     private abstract class ElementComparator implements Comparator<Element> {
 396         public ElementComparator() { }
 397 
 398         /**
 399          * compares two parameter arrays by first comparing the length of the arrays, and
 400          * then each Type of the parameter in the array.
 401          * @param params1 the first parameter array.
 402          * @param params2 the first parameter array.
 403          * @return a negative integer, zero, or a positive integer as the first
 404          *         argument is less than, equal to, or greater than the second.
 405          */
 406         protected int compareParameters(boolean caseSensitive, List<? extends VariableElement> params1,
 407                                         List<? extends VariableElement> params2) {
 408 
 409             return utils.compareStrings(caseSensitive, getParametersAsString(params1),
 410                     getParametersAsString(params2));
 411         }
 412 
 413         String getParametersAsString(List<? extends VariableElement> params) {
 414             StringBuilder sb = new StringBuilder();
 415             for (VariableElement param : params) {
 416                 TypeMirror t = param.asType();
 417                 // prefix P for primitive and R for reference types, thus items will
 418                 // be ordered lexically and correctly.
 419                 sb.append(getTypeCode(t)).append("-").append(t).append("-");
 420             }
 421             return sb.toString();
 422         }
 423 
 424         private String getTypeCode(TypeMirror t) {
 425             return new SimpleTypeVisitor9<String, Void>() {
 426 
 427                 @Override
 428                 public String visitPrimitive(PrimitiveType t, Void p) {
 429                     return "P";
 430                 }
 431                 @Override
 432                 public String visitArray(ArrayType t, Void p) {
 433                     return visit(t.getComponentType());
 434                 }
 435                 @Override
 436                 protected String defaultAction(TypeMirror e, Void p) {
 437                     return "R";
 438                 }
 439 
 440             }.visit(t);
 441         }
 442 
 443         /**
 444          * Compares two Elements, typically the name of a method,
 445          * field or constructor.
 446          * @param e1 the first Element.
 447          * @param e2 the second Element.
 448          * @return a negative integer, zero, or a positive integer as the first
 449          *         argument is less than, equal to, or greater than the second.
 450          */
 451         protected int compareNames(Element e1, Element e2) {
 452             return utils.compareStrings(utils.getSimpleName(e1), utils.getSimpleName(e2));
 453         }
 454 
 455         /**
 456          * Compares the fully qualified names of the entities
 457          * @param e1 the first Element.
 458          * @param e2 the first Element.
 459          * @return a negative integer, zero, or a positive integer as the first
 460          *         argument is less than, equal to, or greater than the second.
 461          */
 462         protected int compareFullyQualifiedNames(Element e1, Element e2) {
 463             // add simplename to be compatible
 464             String thisElement = getFullyQualifiedName(e1);
 465             String thatElement = getFullyQualifiedName(e2);
 466             return utils.compareStrings(thisElement, thatElement);
 467         }
 468 
 469         protected int compareElementKinds(Element e1, Element e2) {
 470             return Integer.compare(getKindIndex(e1), getKindIndex(e2));
 471         }
 472 
 473         private int getKindIndex(Element e) {
 474             switch (e.getKind()) {
 475                 case MODULE:            return 0;
 476                 case PACKAGE:           return 1;
 477                 case CLASS:             return 2;
 478                 case ENUM:              return 3;
 479                 case ENUM_CONSTANT:     return 4;
 480                 case RECORD:            return 5;
 481                 case INTERFACE:         return 6;
 482                 case ANNOTATION_TYPE:   return 7;
 483                 case FIELD:             return 8;
 484                 case CONSTRUCTOR:       return 9;
 485                 case METHOD:            return 10;
 486                 default: throw new IllegalArgumentException(e.getKind().toString());
 487             }
 488         }
 489 
 490         @SuppressWarnings("preview")
 491         boolean hasParameters(Element e) {
 492             return new SimpleElementVisitor14<Boolean, Void>() {
 493                 @Override
 494                 public Boolean visitExecutable(ExecutableElement e, Void p) {
 495                     return true;
 496                 }
 497 
 498                 @Override
 499                 protected Boolean defaultAction(Element e, Void p) {
 500                     return false;
 501                 }
 502 
 503             }.visit(e);
 504         }
 505 
 506         /**
 507          * The fully qualified names of the entities, used solely by the comparator.
 508          *
 509          * @return a negative integer, zero, or a positive integer as the first argument is less
 510          * than, equal to, or greater than the second.
 511          */
 512         @SuppressWarnings("preview")
 513         private String getFullyQualifiedName(Element e) {
 514             return new SimpleElementVisitor14<String, Void>() {
 515                 @Override
 516                 public String visitModule(ModuleElement e, Void p) {
 517                     return e.getQualifiedName().toString();
 518                 }
 519 
 520                 @Override
 521                 public String visitPackage(PackageElement e, Void p) {
 522                     return e.getQualifiedName().toString();
 523                 }
 524 
 525                 @Override
 526                 public String visitExecutable(ExecutableElement e, Void p) {
 527                     // For backward compatibility
 528                     return getFullyQualifiedName(e.getEnclosingElement())
 529                             + "." + e.getSimpleName().toString();
 530                 }
 531 
 532                 @Override
 533                 public String visitType(TypeElement e, Void p) {
 534                     return e.getQualifiedName().toString();
 535                 }
 536 
 537                 @Override
 538                 protected String defaultAction(Element e, Void p) {
 539                     return utils.getEnclosingTypeElement(e).getQualifiedName().toString()
 540                             + "." + e.getSimpleName().toString();
 541                 }
 542             }.visit(e);
 543         }
 544     }
 545 
 546     /**
 547      * Returns a Comparator for SearchIndexItems representing types. Items are
 548      * compared by short name, or full string representation if names are equal.
 549      *
 550      * @return a Comparator
 551      */
 552     public Comparator<SearchIndexItem> makeTypeSearchIndexComparator() {
 553         return (SearchIndexItem sii1, SearchIndexItem sii2) -> {
 554             int result = utils.compareStrings(sii1.getSimpleName(), sii2.getSimpleName());
 555             if (result == 0) {
 556                 // TreeSet needs this to be consistent with equal so we do
 557                 // a plain comparison of string representations as fallback.
 558                 result = sii1.toString().compareTo(sii2.toString());
 559             }
 560             return result;
 561         };
 562     }
 563 
 564     private Comparator<SearchIndexItem> genericSearchIndexComparator = null;
 565 
 566     /**
 567      * Returns a Comparator for SearchIndexItems representing modules, packages, or members.
 568      * Items are compared by label (member name plus signature for members, package name for
 569      * packages, and module name for modules). If labels are equal then full string
 570      * representation is compared.
 571      *
 572      * @return a Comparator
 573      */
 574     public Comparator<SearchIndexItem> makeGenericSearchIndexComparator() {
 575         if (genericSearchIndexComparator == null) {
 576             genericSearchIndexComparator = (SearchIndexItem sii1, SearchIndexItem sii2) -> {
 577                 int result = utils.compareStrings(sii1.getLabel(), sii2.getLabel());
 578                 if (result == 0) {
 579                     // TreeSet needs this to be consistent with equal so we do
 580                     // a plain comparison of string representations as fallback.
 581                     result = sii1.toString().compareTo(sii2.toString());
 582                 }
 583                 return result;
 584             };
 585         }
 586         return genericSearchIndexComparator;
 587     }
 588 
 589 }