diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,3346 @@ +/* + * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.toolkit.util; + +import java.lang.annotation.Documented; +import java.lang.ref.SoftReference; +import java.net.URI; +import java.text.CollationKey; +import java.text.Collator; +import java.util.*; +import java.util.AbstractMap.SimpleEntry; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.ModuleElement.RequiresDirective; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.ElementKindVisitor9; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleElementVisitor9; +import javax.lang.model.util.SimpleTypeVisitor9; +import javax.lang.model.util.TypeKindVisitor9; +import javax.lang.model.util.Types; +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileManager.Location; +import javax.tools.StandardLocation; + +import com.sun.source.doctree.DocCommentTree; +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.DocTree.Kind; +import com.sun.source.doctree.ParamTree; +import com.sun.source.doctree.SerialFieldTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.LineMap; +import com.sun.source.util.DocSourcePositions; +import com.sun.source.util.DocTrees; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.model.JavacTypes; +import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; +import jdk.javadoc.internal.doclets.toolkit.CommentUtils.DocCommentDuo; +import jdk.javadoc.internal.doclets.toolkit.Messages; +import jdk.javadoc.internal.doclets.toolkit.WorkArounds; +import jdk.javadoc.internal.tool.DocEnvImpl; + +import static javax.lang.model.element.ElementKind.*; +import static javax.lang.model.element.Modifier.*; +import static javax.lang.model.type.TypeKind.*; + +import static com.sun.source.doctree.DocTree.Kind.*; +import static jdk.javadoc.internal.doclets.toolkit.builders.ConstantsSummaryBuilder.MAX_CONSTANT_VALUE_INDEX_LENGTH; + + +/** + * Utilities Class for Doclets. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Jamie Ho + */ +public class Utils { + public final BaseConfiguration configuration; + public final Messages messages; + public final DocTrees docTrees; + public final Elements elementUtils; + public final Types typeUtils; + public final JavaScriptScanner javaScriptScanner; + + public Utils(BaseConfiguration c) { + configuration = c; + messages = configuration.getMessages(); + elementUtils = c.docEnv.getElementUtils(); + typeUtils = c.docEnv.getTypeUtils(); + docTrees = c.docEnv.getDocTrees(); + javaScriptScanner = c.isAllowScriptInComments() ? null : new JavaScriptScanner(); + } + + // our own little symbol table + private HashMap symtab = new HashMap<>(); + + public TypeMirror getSymbol(String signature) { + TypeMirror type = symtab.get(signature); + if (type == null) { + TypeElement typeElement = elementUtils.getTypeElement(signature); + if (typeElement == null) + return null; + type = typeElement.asType(); + if (type == null) + return null; + symtab.put(signature, type); + } + return type; + } + + public TypeMirror getObjectType() { + return getSymbol("java.lang.Object"); + } + + public TypeMirror getExceptionType() { + return getSymbol("java.lang.Exception"); + } + + public TypeMirror getErrorType() { + return getSymbol("java.lang.Error"); + } + + public TypeMirror getSerializableType() { + return getSymbol("java.io.Serializable"); + } + + public TypeMirror getExternalizableType() { + return getSymbol("java.io.Externalizable"); + } + + public TypeMirror getIllegalArgumentExceptionType() { + return getSymbol("java.lang.IllegalArgumentException"); + } + + public TypeMirror getNullPointerExceptionType() { + return getSymbol("java.lang.NullPointerException"); + } + + public TypeMirror getDeprecatedType() { + return getSymbol("java.lang.Deprecated"); + } + + public TypeMirror getFunctionalInterface() { + return getSymbol("java.lang.FunctionalInterface"); + } + + /** + * Return array of class members whose documentation is to be generated. + * If the member is deprecated do not include such a member in the + * returned array. + * + * @param members Array of members to choose from. + * @return List List of eligible members for whom + * documentation is getting generated. + */ + public List excludeDeprecatedMembers(List members) { + List excludeList = members.stream() + .filter((member) -> (!isDeprecated(member))) + .sorted(makeGeneralPurposeComparator()) + .collect(Collectors.>toCollection(ArrayList::new)); + return excludeList; + } + + /** + * Search for the given method in the given class. + * + * @param te Class to search into. + * @param method Method to be searched. + * @return ExecutableElement Method found, null otherwise. + */ + public ExecutableElement findMethod(TypeElement te, ExecutableElement method) { + for (Element m : getMethods(te)) { + if (executableMembersEqual(method, (ExecutableElement)m)) { + return (ExecutableElement)m; + } + } + return null; + } + + /** + * Test whether a class is a subclass of another class. + * + * @param t1 the candidate superclass. + * @param t2 the target + * @return true if t1 is a superclass of t2. + */ + public boolean isSubclassOf(TypeElement t1, TypeElement t2) { + return typeUtils.isSubtype(t1.asType(), t2.asType()); + } + + /** + * @param e1 the first method to compare. + * @param e2 the second method to compare. + * @return true if member1 overrides/hides or is overriden/hidden by member2. + */ + + public boolean executableMembersEqual(ExecutableElement e1, ExecutableElement e2) { + // TODO: investigate if Elements.hides(..) will work here. + if (isStatic(e1) && isStatic(e2)) { + List parameters1 = e1.getParameters(); + List parameters2 = e2.getParameters(); + if (e1.getSimpleName().equals(e2.getSimpleName()) && + parameters1.size() == parameters2.size()) { + int j; + for (j = 0 ; j < parameters1.size(); j++) { + VariableElement v1 = parameters1.get(j); + VariableElement v2 = parameters2.get(j); + String t1 = getTypeName(v1.asType(), true); + String t2 = getTypeName(v2.asType(), true); + if (!(t1.equals(t2) || + isTypeVariable(v1.asType()) || isTypeVariable(v2.asType()))) { + break; + } + } + if (j == parameters1.size()) { + return true; + } + } + return false; + } else { + return elementUtils.overrides(e1, e2, getEnclosingTypeElement(e1)) || + elementUtils.overrides(e2, e1, getEnclosingTypeElement(e2)) || + e1.equals(e2); + } + } + + /** + * According to + * The Java™ Language Specification, + * all the outer classes and static inner classes are core classes. + */ + public boolean isCoreClass(TypeElement e) { + return getEnclosingTypeElement(e) == null || isStatic(e); + } + + /** + * Copy doc-files directory and its contents from the source + * package directory to the generated documentation directory. + * For example, given a package java.lang, this method will copy + * the doc-files directory, found in the package directory to the + * generated documentation hierarchy. + * + * @param pe the package containing the doc files to be copied + * @throws DocFileIOException if there is a problem while copying + * the documentation files + */ + public void copyDocFiles(PackageElement pe) throws DocFileIOException { + Location sourceLoc = getLocationForPackage(pe); + copyDirectory(sourceLoc, DocPath.forPackage(pe).resolve(DocPaths.DOC_FILES)); + } + + /** + * Copy the given directory contents from the source package directory + * to the generated documentation directory. For example, given a package + * java.lang, this method will copy the entire directory, to the generated + * documentation hierarchy. + * + * @param pe the package containing the directory to be copied + * @param dir the directory to be copied + * @throws DocFileIOException if there is a problem while copying + * the documentation files + */ + public void copyDirectory(PackageElement pe, DocPath dir) throws DocFileIOException { + copyDirectory(getLocationForPackage(pe), dir); + } + + /** + * Copy the given directory and its contents from the source + * module directory to the generated documentation directory. + * For example, given a package java.lang, this method will + * copy the entire directory, to the generated documentation + * hierarchy. + * + * @param mdle the module containing the directory to be copied + * @param dir the directory to be copied + * @throws DocFileIOException if there is a problem while copying + * the documentation files + */ + public void copyDirectory(ModuleElement mdle, DocPath dir) throws DocFileIOException { + copyDirectory(getLocationForModule(mdle), dir); + } + + /** + * Copy files from a doc path location to the output. + * + * @param locn the location from which to read files + * @param dir the directory to be copied + * @throws DocFileIOException if there is a problem + * copying the files + */ + public void copyDirectory(Location locn, DocPath dir) throws DocFileIOException { + boolean first = true; + for (DocFile f : DocFile.list(configuration, locn, dir)) { + if (!f.isDirectory()) { + continue; + } + DocFile srcdir = f; + DocFile destdir = DocFile.createFileForOutput(configuration, dir); + if (srcdir.isSameFile(destdir)) { + continue; + } + + for (DocFile srcfile: srcdir.list()) { + DocFile destfile = destdir.resolve(srcfile.getName()); + if (srcfile.isFile()) { + if (destfile.exists() && !first) { + messages.warning("doclet.Copy_Overwrite_warning", + srcfile.getPath(), destdir.getPath()); + } else { + messages.notice("doclet.Copying_File_0_To_Dir_1", + srcfile.getPath(), destdir.getPath()); + destfile.copyFile(srcfile); + } + } else if (srcfile.isDirectory()) { + if (configuration.copydocfilesubdirs + && !configuration.shouldExcludeDocFileDir(srcfile.getName())) { + copyDirectory(locn, dir.resolve(srcfile.getName())); + } + } + } + + first = false; + } + } + + protected Location getLocationForPackage(PackageElement pd) { + ModuleElement mdle = configuration.docEnv.getElementUtils().getModuleOf(pd); + + if (mdle == null) + return defaultLocation(); + + return getLocationForModule(mdle); + } + + protected Location getLocationForModule(ModuleElement mdle) { + Location loc = configuration.workArounds.getLocationForModule(mdle); + if (loc != null) + return loc; + + return defaultLocation(); + } + + private Location defaultLocation() { + JavaFileManager fm = configuration.docEnv.getJavaFileManager(); + return fm.hasLocation(StandardLocation.SOURCE_PATH) + ? StandardLocation.SOURCE_PATH + : StandardLocation.CLASS_PATH; + } + + public boolean isAnnotated(TypeMirror e) { + return !e.getAnnotationMirrors().isEmpty(); + } + + public boolean isAnnotated(Element e) { + return !e.getAnnotationMirrors().isEmpty(); + } + + public boolean isAnnotationType(Element e) { + return new SimpleElementVisitor9() { + @Override + public Boolean visitExecutable(ExecutableElement e, Void p) { + return visit(e.getEnclosingElement()); + } + + @Override + public Boolean visitUnknown(Element e, Void p) { + return false; + } + + @Override + protected Boolean defaultAction(Element e, Void p) { + return e.getKind() == ANNOTATION_TYPE; + } + }.visit(e); + } + + /** + * An Enum implementation is almost identical, thus this method returns if + * this element represents a CLASS or an ENUM + * @param e element + * @return true if class or enum + */ + public boolean isClass(Element e) { + return e.getKind().isClass(); + } + + public boolean isConstructor(Element e) { + return e.getKind() == CONSTRUCTOR; + } + + public boolean isEnum(Element e) { + return e.getKind() == ENUM; + } + + boolean isEnumConstant(Element e) { + return e.getKind() == ENUM_CONSTANT; + } + + public boolean isField(Element e) { + return e.getKind() == FIELD; + } + + public boolean isInterface(Element e) { + return e.getKind() == INTERFACE; + } + + public boolean isMethod(Element e) { + return e.getKind() == METHOD; + } + + public boolean isModule(Element e) { + return e.getKind() == ElementKind.MODULE; + } + + public boolean isPackage(Element e) { + return e.getKind() == ElementKind.PACKAGE; + } + + public boolean isAbstract(Element e) { + return e.getModifiers().contains(Modifier.ABSTRACT); + } + + public boolean isDefault(Element e) { + return e.getModifiers().contains(Modifier.DEFAULT); + } + + public boolean isPackagePrivate(Element e) { + return !(isPublic(e) || isPrivate(e) || isProtected(e)); + } + + public boolean isPrivate(Element e) { + return e.getModifiers().contains(Modifier.PRIVATE); + } + + public boolean isProtected(Element e) { + return e.getModifiers().contains(Modifier.PROTECTED); + } + + public boolean isPublic(Element e) { + return e.getModifiers().contains(Modifier.PUBLIC); + } + + public boolean isProperty(String name) { + return configuration.javafx && name.endsWith("Property"); + } + + public String getPropertyName(String name) { + return isProperty(name) + ? name.substring(0, name.length() - "Property".length()) + : name; + } + + public String getPropertyLabel(String name) { + return name.substring(0, name.lastIndexOf("Property")); + } + + public boolean isOverviewElement(Element e) { + return e.getKind() == ElementKind.OTHER; + } + + public boolean isStatic(Element e) { + return e.getModifiers().contains(Modifier.STATIC); + } + + public boolean isSerializable(TypeElement e) { + return typeUtils.isSubtype(e.asType(), getSerializableType()); + } + + public boolean isExternalizable(TypeElement e) { + return typeUtils.isSubtype(e.asType(), getExternalizableType()); + } + + public SortedSet serializableFields(TypeElement aclass) { + return configuration.workArounds.getSerializableFields(this, aclass); + } + + public SortedSet serializationMethods(TypeElement aclass) { + return configuration.workArounds.getSerializationMethods(this, aclass); + } + + public boolean definesSerializableFields(TypeElement aclass) { + return configuration.workArounds.definesSerializableFields(this, aclass); + } + + public String modifiersToString(Element e, boolean trailingSpace) { + SortedSet set = new TreeSet<>(e.getModifiers()); + set.remove(Modifier.NATIVE); + set.remove(Modifier.STRICTFP); + set.remove(Modifier.SYNCHRONIZED); + + return new ElementKindVisitor9>() { + final StringBuilder sb = new StringBuilder(); + + void addVisibilityModifier(Set modifiers) { + if (modifiers.contains(PUBLIC)) { + sb.append("public").append(" "); + } else if (modifiers.contains(PROTECTED)) { + sb.append("protected").append(" "); + } else if (modifiers.contains(PRIVATE)) { + sb.append("private").append(" "); + } + } + + void addStatic(Set modifiers) { + if (modifiers.contains(STATIC)) { + sb.append("static").append(" "); + } + } + + void addModifers(Set modifiers) { + String s = set.stream().map(Modifier::toString).collect(Collectors.joining(" ")); + sb.append(s); + if (!s.isEmpty()) + sb.append(" "); + } + + String finalString(String s) { + sb.append(s); + if (trailingSpace) { + if (sb.lastIndexOf(" ") == sb.length() - 1) { + return sb.toString(); + } else { + return sb.append(" ").toString(); + } + } else { + return sb.toString().trim(); + } + } + + @Override + public String visitTypeAsInterface(TypeElement e, SortedSet p) { + addVisibilityModifier(p); + addStatic(p); + return finalString("interface"); + } + + @Override + public String visitTypeAsEnum(TypeElement e, SortedSet p) { + addVisibilityModifier(p); + addStatic(p); + return finalString("enum"); + } + + @Override + public String visitTypeAsAnnotationType(TypeElement e, SortedSet p) { + addVisibilityModifier(p); + addStatic(p); + return finalString("@interface"); + } + + @Override + public String visitTypeAsClass(TypeElement e, SortedSet p) { + addModifers(p); + return finalString("class"); + } + + @Override + protected String defaultAction(Element e, SortedSet p) { + addModifers(p); + return sb.toString().trim(); + } + + }.visit(e, set); + } + + public boolean isFunctionalInterface(AnnotationMirror amirror) { + return amirror.getAnnotationType().equals(getFunctionalInterface()) && + configuration.docEnv.getSourceVersion() + .compareTo(SourceVersion.RELEASE_8) >= 0; + } + + public boolean isNoType(TypeMirror t) { + return t.getKind() == NONE; + } + + public boolean isOrdinaryClass(TypeElement te) { + if (isEnum(te) || isInterface(te) || isAnnotationType(te)) { + return false; + } + if (isError(te) || isException(te)) { + return false; + } + return true; + } + + public boolean isError(TypeElement te) { + if (isEnum(te) || isInterface(te) || isAnnotationType(te)) { + return false; + } + return typeUtils.isSubtype(te.asType(), getErrorType()); + } + + public boolean isException(TypeElement te) { + if (isEnum(te) || isInterface(te) || isAnnotationType(te)) { + return false; + } + return typeUtils.isSubtype(te.asType(), getExceptionType()); + } + + public boolean isPrimitive(TypeMirror t) { + return new SimpleTypeVisitor9() { + + @Override + public Boolean visitNoType(NoType t, Void p) { + return t.getKind() == VOID; + } + @Override + public Boolean visitPrimitive(PrimitiveType t, Void p) { + return true; + } + @Override + public Boolean visitArray(ArrayType t, Void p) { + return visit(t.getComponentType()); + } + @Override + protected Boolean defaultAction(TypeMirror e, Void p) { + return false; + } + }.visit(t); + } + + public boolean isExecutableElement(Element e) { + ElementKind kind = e.getKind(); + switch (kind) { + case CONSTRUCTOR: case METHOD: case INSTANCE_INIT: + return true; + default: + return false; + } + } + + public boolean isVariableElement(Element e) { + ElementKind kind = e.getKind(); + switch(kind) { + case ENUM_CONSTANT: case EXCEPTION_PARAMETER: case FIELD: + case LOCAL_VARIABLE: case PARAMETER: + case RESOURCE_VARIABLE: + return true; + default: + return false; + } + } + + public boolean isTypeElement(Element e) { + switch (e.getKind()) { + case CLASS: case ENUM: case INTERFACE: case ANNOTATION_TYPE: + return true; + default: + return false; + } + } + + /** + * Get the signature. It is the parameter list, type is qualified. + * For instance, for a method {@code mymethod(String x, int y)}, + * it will return {@code(java.lang.String,int)}. + * + * @param e + * @return String + */ + public String signature(ExecutableElement e) { + return makeSignature(e, true); + } + + /** + * Get flat signature. All types are not qualified. + * Return a String, which is the flat signature of this member. + * It is the parameter list, type is not qualified. + * For instance, for a method {@code mymethod(String x, int y)}, + * it will return {@code (String, int)}. + */ + public String flatSignature(ExecutableElement e) { + return makeSignature(e, false); + } + + public String makeSignature(ExecutableElement e, boolean full) { + return makeSignature(e, full, false); + } + + public String makeSignature(ExecutableElement e, boolean full, boolean ignoreTypeParameters) { + StringBuilder result = new StringBuilder(); + result.append("("); + Iterator iterator = e.getParameters().iterator(); + while (iterator.hasNext()) { + VariableElement next = iterator.next(); + TypeMirror type = next.asType(); + result.append(getTypeSignature(type, full, ignoreTypeParameters)); + if (iterator.hasNext()) { + result.append(", "); + } + } + if (e.isVarArgs()) { + int len = result.length(); + result.replace(len - 2, len, "..."); + } + result.append(")"); + return result.toString(); + } + + public String getTypeSignature(TypeMirror t, boolean qualifiedName, boolean noTypeParameters) { + return new SimpleTypeVisitor9() { + final StringBuilder sb = new StringBuilder(); + + @Override + public StringBuilder visitArray(ArrayType t, Void p) { + TypeMirror componentType = t.getComponentType(); + visit(componentType); + sb.append("[]"); + return sb; + } + + @Override + public StringBuilder visitDeclared(DeclaredType t, Void p) { + Element e = t.asElement(); + sb.append(qualifiedName ? getFullyQualifiedName(e) : getSimpleName(e)); + List typeArguments = t.getTypeArguments(); + if (typeArguments.isEmpty() || noTypeParameters) { + return sb; + } + sb.append("<"); + Iterator iterator = typeArguments.iterator(); + while (iterator.hasNext()) { + TypeMirror ta = iterator.next(); + visit(ta); + if (iterator.hasNext()) { + sb.append(", "); + } + } + sb.append(">"); + return sb; + } + + @Override + public StringBuilder visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) { + Element e = t.asElement(); + sb.append(qualifiedName ? getFullyQualifiedName(e, false) : getSimpleName(e)); + return sb; + } + + @Override + public StringBuilder visitWildcard(javax.lang.model.type.WildcardType t, Void p) { + sb.append("?"); + TypeMirror upperBound = t.getExtendsBound(); + if (upperBound != null) { + sb.append(" extends "); + visit(upperBound); + } + TypeMirror superBound = t.getSuperBound(); + if (superBound != null) { + sb.append(" super "); + visit(superBound); + } + return sb; + } + + @Override + protected StringBuilder defaultAction(TypeMirror e, Void p) { + return sb.append(e); + } + }.visit(t).toString(); + } + + public boolean isArrayType(TypeMirror t) { + return t.getKind() == ARRAY; + } + + public boolean isDeclaredType(TypeMirror t) { + return t.getKind() == DECLARED; + } + + public boolean isErrorType(TypeMirror t) { + return t.getKind() == ERROR; + } + + public boolean isIntersectionType(TypeMirror t) { + return t.getKind() == INTERSECTION; + } + + public boolean isTypeParameterElement(Element e) { + return e.getKind() == TYPE_PARAMETER; + } + + public boolean isTypeVariable(TypeMirror t) { + return t.getKind() == TYPEVAR; + } + + public boolean isVoid(TypeMirror t) { + return t.getKind() == VOID; + } + + public boolean isWildCard(TypeMirror t) { + return t.getKind() == WILDCARD; + } + + public boolean ignoreBounds(TypeMirror bound) { + return bound.equals(getObjectType()) && !isAnnotated(bound); + } + + /* + * a direct port of TypeVariable.getBounds + */ + public List getBounds(TypeParameterElement tpe) { + List bounds = tpe.getBounds(); + if (!bounds.isEmpty()) { + TypeMirror upperBound = bounds.get(bounds.size() - 1); + if (ignoreBounds(upperBound)) { + return Collections.emptyList(); + } + } + return bounds; + } + + /** + * Returns the TypeMirror of the ExecutableElement for all methods, + * a null if constructor. + * @param ee the ExecutableElement + * @return + */ + public TypeMirror getReturnType(ExecutableElement ee) { + return ee.getKind() == CONSTRUCTOR ? null : ee.getReturnType(); + } + + /** + * Return the type containing the method that this method overrides. + * It may be a {@code TypeElement} or a {@code TypeParameterElement}. + */ + public TypeMirror overriddenType(ExecutableElement method) { + return configuration.workArounds.overriddenType(method); + } + + private TypeMirror getType(TypeMirror t) { + return (isNoType(t)) ? getObjectType() : t; + } + + public TypeMirror getSuperType(TypeElement te) { + TypeMirror t = te.getSuperclass(); + return getType(t); + } + + /** + * Return the class that originally defined the method that + * is overridden by the current definition, or null if no + * such class exists. + * + * @return a TypeElement representing the superclass that + * originally defined this method, null if this method does + * not override a definition in a superclass. + */ + public TypeElement overriddenClass(ExecutableElement ee) { + TypeMirror type = overriddenType(ee); + return (type != null) ? asTypeElement(type) : null; + } + + public ExecutableElement overriddenMethod(ExecutableElement method) { + if (isStatic(method)) { + return null; + } + final TypeElement origin = getEnclosingTypeElement(method); + for (TypeMirror t = getSuperType(origin); + t.getKind() == DECLARED; + t = getSuperType(asTypeElement(t))) { + TypeElement te = asTypeElement(t); + if (te == null) { + return null; + } + List methods = te.getEnclosedElements(); + for (ExecutableElement ee : ElementFilter.methodsIn(methods)) { + if (configuration.workArounds.overrides(method, ee, origin)) { + return ee; + } + } + if (t.equals(getObjectType())) + return null; + } + return null; + } + + public SortedSet getTypeElementsAsSortedSet(Iterable typeElements) { + SortedSet set = new TreeSet<>(makeGeneralPurposeComparator()); + for (TypeElement te : typeElements) { + set.add(te); + } + return set; + } + + public List getSerialDataTrees(ExecutableElement member) { + return getBlockTags(member, SERIAL_DATA); + } + + public FileObject getFileObject(TypeElement te) { + return docTrees.getPath(te).getCompilationUnit().getSourceFile(); + } + + public TypeMirror getDeclaredType(TypeElement enclosing, TypeMirror target) { + return getDeclaredType(Collections.emptyList(), enclosing, target); + } + + /** + * Finds the declaration of the enclosing's type parameter. + * + * @param values + * @param enclosing a TypeElement whose type arguments we desire + * @param target the TypeMirror of the type as described by the enclosing + * @return + */ + public TypeMirror getDeclaredType(Collection values, + TypeElement enclosing, TypeMirror target) { + TypeElement targetElement = asTypeElement(target); + List targetTypeArgs = targetElement.getTypeParameters(); + if (targetTypeArgs.isEmpty()) { + return target; + } + + List enclosingTypeArgs = enclosing.getTypeParameters(); + List targetTypeArgTypes = new ArrayList<>(targetTypeArgs.size()); + + if (enclosingTypeArgs.isEmpty()) { + for (TypeMirror te : values) { + List typeArguments = ((DeclaredType)te).getTypeArguments(); + if (typeArguments.size() >= targetTypeArgs.size()) { + for (int i = 0 ; i < targetTypeArgs.size(); i++) { + targetTypeArgTypes.add(typeArguments.get(i)); + } + break; + } + } + // we found no matches in the hierarchy + if (targetTypeArgTypes.isEmpty()) { + return target; + } + } else { + if (targetTypeArgs.size() > enclosingTypeArgs.size()) { + return target; + } + for (int i = 0; i < targetTypeArgs.size(); i++) { + TypeParameterElement tpe = enclosingTypeArgs.get(i); + targetTypeArgTypes.add(tpe.asType()); + } + } + TypeMirror dt = typeUtils.getDeclaredType(targetElement, + targetTypeArgTypes.toArray(new TypeMirror[targetTypeArgTypes.size()])); + return dt; + } + + /** + * For the class return all implemented interfaces including the + * superinterfaces of the implementing interfaces, also iterate over for + * all the superclasses. For interface return all the extended interfaces + * as well as superinterfaces for those extended interfaces. + * + * @param te the class to get the interfaces for + * @return List of all the required interfaces. + */ + public Set getAllInterfaces(TypeElement te) { + Set results = new LinkedHashSet<>(); + + List interfaceTypes = te.getInterfaces(); + + for (TypeMirror interfaceType : interfaceTypes) { + TypeElement intfc = asTypeElement(interfaceType); + + if (isPublic(intfc) || isLinkable(intfc)) { + results.add(interfaceType); + TypeElement klass = asTypeElement(interfaceType); + for (TypeMirror t : getAllInterfaces(klass)) { + t = getDeclaredType(results, te, t); + results.add(t); + } + } + } + // TypeMirror contains the modified TypeParameterElement's types represented + // in the local Class'es elements types. ex: Foo implements Bar and the + // class being considered is Foo then TypeParameters will be represented as + // note that any conversion might revert back to the old signature. For this + // very reason we get the superType, and find its interfaces. + TypeMirror superType = getSuperType(te); + if (superType == getObjectType()) + return results; + // Try walking the tree + addAllInterfaceTypes(results, te, superType, + configuration.workArounds.interfaceTypesOf(superType)); + return results; + } + + private void findAllInterfaceTypes(Set results, final TypeElement baseClass, + TypeMirror p) { + TypeMirror superType = getSuperType(asTypeElement(p)); + if (superType == p) { + return; + } + addAllInterfaceTypes(results, baseClass, superType, + configuration.workArounds.interfaceTypesOf(superType)); + } + + private void addAllInterfaceTypes(Set results, + final TypeElement baseClass, TypeMirror type, + List interfaceTypes) { + for (TypeMirror interfaceType : interfaceTypes) { + TypeElement iElement = asTypeElement(interfaceType); + if (isPublic(iElement) && isLinkable(iElement)) { + interfaceType = getDeclaredType(results, baseClass, interfaceType); + results.add(interfaceType); + Set superInterfaces = getAllInterfaces(iElement); + for (TypeMirror superInterface : superInterfaces) { + superInterface = getDeclaredType(results, baseClass, superInterface); + results.add(superInterface); + } + } + } + findAllInterfaceTypes(results, baseClass, type); + } + + /** + * Lookup for a class within this package. + * + * @return TypeElement of found class, or null if not found. + */ + public TypeElement findClassInPackageElement(PackageElement pkg, String className) { + for (TypeElement c : getAllClasses(pkg)) { + if (getSimpleName(c).equals(className)) { + return c; + } + } + return null; + } + + /** + * TODO: FIXME: port to javax.lang.model + * Find a class within the context of this class. Search order: qualified name, in this class + * (inner), in this package, in the class imports, in the package imports. Return the + * TypeElement if found, null if not found. + */ + //### The specified search order is not the normal rule the + //### compiler would use. Leave as specified or change it? + public TypeElement findClass(Element element, String className) { + TypeElement encl = getEnclosingTypeElement(element); + TypeElement searchResult = configuration.workArounds.searchClass(encl, className); + if (searchResult == null) { + encl = getEnclosingTypeElement(encl); + //Expand search space to include enclosing class. + while (encl != null && getEnclosingTypeElement(encl) != null) { + encl = getEnclosingTypeElement(encl); + } + searchResult = encl == null + ? null + : configuration.workArounds.searchClass(encl, className); + } + return searchResult; + } + + /** + * Enclose in quotes, used for paths and filenames that contains spaces + */ + public String quote(String filepath) { + return ("\"" + filepath + "\""); + } + + /** + * Parse the package name. We only want to display package name up to + * 2 levels. + */ + public String parsePackageName(PackageElement p) { + String pkgname = p.isUnnamed() ? "" : getPackageName(p); + int index = -1; + for (int j = 0; j < MAX_CONSTANT_VALUE_INDEX_LENGTH; j++) { + index = pkgname.indexOf(".", index + 1); + } + if (index != -1) { + pkgname = pkgname.substring(0, index); + } + return pkgname; + } + + /** + * Given a string, replace all occurrences of 'newStr' with 'oldStr'. + * @param originalStr the string to modify. + * @param oldStr the string to replace. + * @param newStr the string to insert in place of the old string. + */ + public String replaceText(String originalStr, String oldStr, + String newStr) { + if (oldStr == null || newStr == null || oldStr.equals(newStr)) { + return originalStr; + } + return originalStr.replace(oldStr, newStr); + } + + /** + * Given an annotation, return true if it should be documented and false + * otherwise. + * + * @param annotation the annotation to check. + * + * @return true return true if it should be documented and false otherwise. + */ + public boolean isDocumentedAnnotation(TypeElement annotation) { + for (AnnotationMirror anno : annotation.getAnnotationMirrors()) { + if (getFullyQualifiedName(anno.getAnnotationType().asElement()).equals( + Documented.class.getName())) { + return true; + } + } + return false; + } + + /** + * Return true if this class is linkable and false if we can't link to the + * desired class. + *
+ * NOTE: You can only link to external classes if they are public or + * protected. + * + * @return true if this class is linkable and false if we can't link to the + * desired class. + */ + public boolean isLinkable(TypeElement typeElem) { + return + (typeElem != null && + (isIncluded(typeElem) && configuration.isGeneratedDoc(typeElem))) || + (configuration.extern.isExternal(typeElem) && + (isPublic(typeElem) || isProtected(typeElem))); + } + + List asErasureTypes(Collection inList) { + List out = new ArrayList<>(inList.size()); + inList.stream().forEach((te) -> { + out.add(typeUtils.erasure(te.asType())); + }); + return out; + } + + List asTypes(Collection inList) { + List out = new ArrayList<>(inList.size()); + inList.stream().forEach((te) -> { + out.add(te.asType()); + }); + return out; + } + + /** + * Return this type as a {@code TypeElement} if it represents a class + * interface or annotation. Array dimensions are ignored. + * If this type {@code ParameterizedType} or {@code WildcardType}, return + * the {@code TypeElement} of the type's erasure. If this is an + * annotation, return this as a {@code TypeElement}. + * If this is a primitive type, return null. + * + * @return the {@code TypeElement} of this type, + * or null if it is a primitive type. + */ + public TypeElement asTypeElement(TypeMirror t) { + return new SimpleTypeVisitor9() { + + @Override + public TypeElement visitDeclared(DeclaredType t, Void p) { + return (TypeElement) t.asElement(); + } + + @Override + public TypeElement visitArray(ArrayType t, Void p) { + return visit(t.getComponentType()); + } + + @Override + public TypeElement visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) { + /* + * TODO: Check with JJG. + * if we have an annotated type @A $B T, then erasure returns a + * none, in this case we use asElement instead. + */ + if (isAnnotated(t)) { + return visit(typeUtils.asElement(t).asType()); + } + return visit(typeUtils.erasure(t)); + } + + @Override + public TypeElement visitWildcard(javax.lang.model.type.WildcardType t, Void p) { + return visit(typeUtils.erasure(t)); + } + + @Override + public TypeElement visitError(ErrorType t, Void p) { + return (TypeElement)t.asElement(); + } + + @Override + protected TypeElement defaultAction(TypeMirror e, Void p) { + return super.defaultAction(e, p); + } + }.visit(t); + } + + public TypeMirror getComponentType(TypeMirror t) { + while (isArrayType(t)) { + t = ((ArrayType) t).getComponentType(); + } + return t; + } + + /** + * Return the type's dimension information, as a string. + *

+ * For example, a two dimensional array of String returns "{@code [][]}". + * + * @return the type's dimension information as a string. + */ + public String getDimension(TypeMirror t) { + return new SimpleTypeVisitor9() { + StringBuilder dimension = new StringBuilder(""); + @Override + public String visitArray(ArrayType t, Void p) { + dimension.append("[]"); + return visit(t.getComponentType()); + } + + @Override + protected String defaultAction(TypeMirror e, Void p) { + return dimension.toString(); + } + + }.visit(t); + } + + public TypeElement getSuperClass(TypeElement te) { + if (isInterface(te) || isAnnotationType(te) || + te.asType().equals(getObjectType())) { + return null; + } + TypeMirror superclass = te.getSuperclass(); + if (isNoType(superclass) && isClass(te)) { + superclass = getObjectType(); + } + return asTypeElement(superclass); + } + + public TypeElement getFirstVisibleSuperClassAsTypeElement(TypeElement te) { + if (isAnnotationType(te) || isInterface(te) || + te.asType().equals(getObjectType())) { + return null; + } + TypeMirror firstVisibleSuperClass = getFirstVisibleSuperClass(te); + return firstVisibleSuperClass == null ? null : asTypeElement(firstVisibleSuperClass); + } + + /** + * Given a class, return the closest visible super class. + * @param type the TypeMirror to be interrogated + * @return the closest visible super class. Return null if it cannot + * be found. + */ + + public TypeMirror getFirstVisibleSuperClass(TypeMirror type) { + return getFirstVisibleSuperClass(asTypeElement(type)); + } + + + /** + * Given a class, return the closest visible super class. + * + * @param te the TypeElement to be interrogated + * @return the closest visible super class. Return null if it cannot + * be found.. + */ + public TypeMirror getFirstVisibleSuperClass(TypeElement te) { + TypeMirror superType = te.getSuperclass(); + if (isNoType(superType)) { + superType = getObjectType(); + } + TypeElement superClass = asTypeElement(superType); + // skip "hidden" classes + while ((superClass != null && isHidden(superClass)) + || (superClass != null && !isPublic(superClass) && !isLinkable(superClass))) { + TypeMirror supersuperType = superClass.getSuperclass(); + TypeElement supersuperClass = asTypeElement(supersuperType); + if (supersuperClass == null + || supersuperClass.getQualifiedName().equals(superClass.getQualifiedName())) { + break; + } + superType = supersuperType; + superClass = supersuperClass; + } + if (te.asType().equals(superType)) { + return null; + } + return superType; + } + + /** + * Given a TypeElement, return the name of its type (Class, Interface, etc.). + * + * @param te the TypeElement to check. + * @param lowerCaseOnly true if you want the name returned in lower case. + * If false, the first letter of the name is capitalized. + * @return + */ + + public String getTypeElementName(TypeElement te, boolean lowerCaseOnly) { + String typeName = ""; + if (isInterface(te)) { + typeName = "doclet.Interface"; + } else if (isException(te)) { + typeName = "doclet.Exception"; + } else if (isError(te)) { + typeName = "doclet.Error"; + } else if (isAnnotationType(te)) { + typeName = "doclet.AnnotationType"; + } else if (isEnum(te)) { + typeName = "doclet.Enum"; + } else if (isOrdinaryClass(te)) { + typeName = "doclet.Class"; + } + typeName = lowerCaseOnly ? toLowerCase(typeName) : typeName; + return typeNameMap.computeIfAbsent(typeName, configuration :: getText); + } + + private final Map typeNameMap = new HashMap<>(); + + public String getTypeName(TypeMirror t, boolean fullyQualified) { + return new SimpleTypeVisitor9() { + + @Override + public String visitArray(ArrayType t, Void p) { + return visit(t.getComponentType()); + } + + @Override + public String visitDeclared(DeclaredType t, Void p) { + TypeElement te = asTypeElement(t); + return fullyQualified + ? te.getQualifiedName().toString() + : getSimpleName(te); + } + + @Override + public String visitExecutable(ExecutableType t, Void p) { + return t.toString(); + } + + @Override + public String visitPrimitive(PrimitiveType t, Void p) { + return t.toString(); + } + + @Override + public String visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) { + return getSimpleName(t.asElement()); + } + + @Override + public String visitWildcard(javax.lang.model.type.WildcardType t, Void p) { + return t.toString(); + } + + @Override + protected String defaultAction(TypeMirror e, Void p) { + return e.toString(); + } + }.visit(t); + } + + /** + * Replace all tabs in a string with the appropriate number of spaces. + * The string may be a multi-line string. + * @param text the text for which the tabs should be expanded + * @return the text with all tabs expanded + */ + public String replaceTabs(String text) { + if (!text.contains("\t")) + return text; + + final int tabLength = configuration.sourcetab; + final String whitespace = configuration.tabSpaces; + final int textLength = text.length(); + StringBuilder result = new StringBuilder(textLength); + int pos = 0; + int lineLength = 0; + for (int i = 0; i < textLength; i++) { + char ch = text.charAt(i); + switch (ch) { + case '\n': case '\r': + lineLength = 0; + break; + case '\t': + result.append(text, pos, i); + int spaceCount = tabLength - lineLength % tabLength; + result.append(whitespace, 0, spaceCount); + lineLength += spaceCount; + pos = i + 1; + break; + default: + lineLength++; + } + } + result.append(text, pos, textLength); + return result.toString(); + } + + public CharSequence normalizeNewlines(CharSequence text) { + StringBuilder sb = new StringBuilder(); + final int textLength = text.length(); + final String NL = DocletConstants.NL; + int pos = 0; + for (int i = 0; i < textLength; i++) { + char ch = text.charAt(i); + switch (ch) { + case '\n': + sb.append(text, pos, i); + sb.append(NL); + pos = i + 1; + break; + case '\r': + sb.append(text, pos, i); + sb.append(NL); + if (i + 1 < textLength && text.charAt(i + 1) == '\n') + i++; + pos = i + 1; + break; + } + } + sb.append(text, pos, textLength); + return sb; + } + + /** + * The documentation for values() and valueOf() in Enums are set by the + * doclet only iff the user or overridden methods are missing. + * @param elem + */ + public void setEnumDocumentation(TypeElement elem) { + for (Element e : getMethods(elem)) { + ExecutableElement ee = (ExecutableElement)e; + if (!getFullBody(e).isEmpty()) // ignore if already set + continue; + if (ee.getSimpleName().contentEquals("values") && ee.getParameters().isEmpty()) { + configuration.cmtUtils.setEnumValuesTree(configuration, e); + } + if (ee.getSimpleName().contentEquals("valueOf") && ee.getParameters().size() == 1) { + configuration.cmtUtils.setEnumValueOfTree(configuration, e); + } + } + } + + /** + * Returns a locale independent lower cased String. That is, it + * always uses US locale, this is a clone of the one in StringUtils. + * @param s to convert + * @return converted String + */ + public static String toLowerCase(String s) { + return s.toLowerCase(Locale.US); + } + + /** + * Return true if the given Element is deprecated. + * + * @param e the Element to check. + * @return true if the given Element is deprecated. + */ + public boolean isDeprecated(Element e) { + if (isPackage(e)) { + return configuration.workArounds.isDeprecated0(e); + } + return elementUtils.isDeprecated(e); + } + + /** + * Return true if the given Element is deprecated for removal. + * + * @param e the Element to check. + * @return true if the given Element is deprecated for removal. + */ + public boolean isDeprecatedForRemoval(Element e) { + List annotationList = e.getAnnotationMirrors(); + JavacTypes jctypes = ((DocEnvImpl) configuration.docEnv).toolEnv.typeutils; + for (AnnotationMirror anno : annotationList) { + if (jctypes.isSameType(anno.getAnnotationType().asElement().asType(), getDeprecatedType())) { + Map pairs = anno.getElementValues(); + if (!pairs.isEmpty()) { + for (ExecutableElement element : pairs.keySet()) { + if (element.getSimpleName().contentEquals("forRemoval")) { + return Boolean.parseBoolean((pairs.get(element)).toString()); + } + } + } + } + } + return false; + } + + /** + * A convenience method to get property name from the name of the + * getter or setter method. + * @param e the input method. + * @return the name of the property of the given setter of getter. + */ + public String propertyName(ExecutableElement e) { + String name = getSimpleName(e); + String propertyName = null; + if (name.startsWith("get") || name.startsWith("set")) { + propertyName = name.substring(3); + } else if (name.startsWith("is")) { + propertyName = name.substring(2); + } + if ((propertyName == null) || propertyName.isEmpty()){ + return ""; + } + return propertyName.substring(0, 1).toLowerCase(configuration.getLocale()) + + propertyName.substring(1); + } + + /** + * Returns true if the element is included, contains @hidden tag, + * or if javafx flag is present and element contains @treatAsPrivate + * tag. + * @param e the queried element + * @return true if it exists, false otherwise + */ + public boolean isHidden(Element e) { + // prevent needless tests on elements which are not included + if (!isIncluded(e)) { + return false; + } + if (configuration.javafx && + hasBlockTag(e, DocTree.Kind.UNKNOWN_BLOCK_TAG, "treatAsPrivate")) { + return true; + } + return hasBlockTag(e, DocTree.Kind.HIDDEN); + } + + /** + * In case of JavaFX mode on, filters out classes that are private, + * package private, these are not documented in JavaFX mode, also + * remove those classes that have @hidden or @treatAsPrivate comment tag. + * + * @param classlist a collection of TypeElements + * @param javafx set to true if in JavaFX mode. + * @return list of filtered classes. + */ + public SortedSet filterOutPrivateClasses(Iterable classlist, + boolean javafx) { + SortedSet filteredOutClasses = + new TreeSet<>(makeGeneralPurposeComparator()); + if (!javafx) { + for (Element te : classlist) { + if (!isHidden(te)) { + filteredOutClasses.add((TypeElement)te); + } + } + return filteredOutClasses; + } + for (Element e : classlist) { + if (isPrivate(e) || isPackagePrivate(e) || isHidden(e)) { + continue; + } + filteredOutClasses.add((TypeElement)e); + } + return filteredOutClasses; + } + + /** + * Compares two elements. + * @param e1 first Element + * @param e2 second Element + * @return a true if they are the same, false otherwise. + */ + public boolean elementsEqual(Element e1, Element e2) { + if (e1.getKind() != e2.getKind()) { + return false; + } + String s1 = getSimpleName(e1); + String s2 = getSimpleName(e2); + if (compareStrings(s1, s2) == 0) { + String f1 = getFullyQualifiedName(e1, true); + String f2 = getFullyQualifiedName(e2, true); + return compareStrings(f1, f2) == 0; + } + return false; + } + + /** + * A general purpose case insensitive String comparator, which compares + * two Strings using a Collator strength of "TERTIARY". + * + * @param s1 first String to compare. + * @param s2 second String to compare. + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second. + */ + public int compareStrings(String s1, String s2) { + return compareStrings(true, s1, s2); + } + + /** + * A general purpose case sensitive String comparator, which + * compares two Strings using a Collator strength of "SECONDARY". + * + * @param s1 first String to compare. + * @param s2 second String to compare. + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second. + */ + public int compareCaseCompare(String s1, String s2) { + return compareStrings(false, s1, s2); + } + + private DocCollator tertiaryCollator = null; + private DocCollator secondaryCollator = null; + + private int compareStrings(boolean caseSensitive, String s1, String s2) { + if (caseSensitive) { + if (tertiaryCollator == null) { + tertiaryCollator = new DocCollator(configuration.locale, Collator.TERTIARY); + } + return tertiaryCollator.compare(s1, s2); + } + if (secondaryCollator == null) { + secondaryCollator = new DocCollator(configuration.locale, Collator.SECONDARY); + } + return secondaryCollator.compare(s1, s2); + } + + private static class DocCollator { + private final Map keys; + private final Collator instance; + private final int MAX_SIZE = 1000; + private DocCollator(Locale locale, int strength) { + instance = Collator.getInstance(locale); + instance.setStrength(strength); + + keys = new LinkedHashMap(MAX_SIZE + 1, 0.75f, true) { + private static final long serialVersionUID = 1L; + @Override + protected boolean removeEldestEntry(Entry eldest) { + return size() > MAX_SIZE; + } + }; + } + + CollationKey getKey(String s) { + return keys.computeIfAbsent(s, instance :: getCollationKey); + } + + public int compare(String s1, String s2) { + return getKey(s1).compareTo(getKey(s2)); + } + } + + private Comparator moduleComparator = null; + /** + * Comparator for ModuleElements, simply compares the fully qualified names + * @return a Comparator + */ + public Comparator makeModuleComparator() { + if (moduleComparator == null) { + moduleComparator = new Utils.ElementComparator() { + @Override + public int compare(Element mod1, Element mod2) { + return compareFullyQualifiedNames(mod1, mod2); + } + }; + } + return moduleComparator; + } + + private Comparator allClassesComparator = null; + /** + * Returns a Comparator for all classes, compares the simple names of + * TypeElement, if equal then the fully qualified names. + * + * @return Comparator + */ + public Comparator makeAllClassesComparator() { + if (allClassesComparator == null) { + allClassesComparator = new Utils.ElementComparator() { + @Override + public int compare(Element e1, Element e2) { + int result = compareNames(e1, e2); + if (result == 0) + result = compareFullyQualifiedNames(e1, e2); + + return result; + } + }; + } + return allClassesComparator; + } + + private Comparator packageComparator = null; + /** + * Returns a Comparator for packages, by comparing the fully qualified names. + * + * @return a Comparator + */ + public Comparator makePackageComparator() { + if (packageComparator == null) { + packageComparator = new Utils.ElementComparator() { + @Override + public int compare(Element pkg1, Element pkg2) { + return compareFullyQualifiedNames(pkg1, pkg2); + } + }; + } + return packageComparator; + } + + private Comparator serialFieldTreeComparator = null; + /** + * Returns a Comparator for SerialFieldTree. + * @return a Comparator + */ + public Comparator makeSerialFieldTreeComparator() { + if (serialFieldTreeComparator == null) { + serialFieldTreeComparator = (SerialFieldTree o1, SerialFieldTree o2) -> { + String s1 = o1.getName().toString(); + String s2 = o2.getName().toString(); + return s1.compareTo(s2); + }; + } + return serialFieldTreeComparator; + } + + /** + * Returns a general purpose comparator. + * @return a Comparator + */ + public Comparator makeGeneralPurposeComparator() { + return makeClassUseComparator(); + } + + private Comparator overrideUseComparator = null; + /** + * Returns a Comparator for overrides and implements, + * used primarily on methods, compares the name first, + * then compares the simple names of the enclosing + * TypeElement and the fully qualified name of the enclosing TypeElement. + * @return a Comparator + */ + public Comparator makeOverrideUseComparator() { + if (overrideUseComparator == null) { + overrideUseComparator = new Utils.ElementComparator() { + @Override + public int compare(Element o1, Element o2) { + int result = compareStrings(getSimpleName(o1), getSimpleName(o2)); + if (result != 0) { + return result; + } + if (!isTypeElement(o1) && !isTypeElement(o2) && !isPackage(o1) && !isPackage(o2)) { + TypeElement t1 = getEnclosingTypeElement(o1); + TypeElement t2 = getEnclosingTypeElement(o2); + result = compareStrings(getSimpleName(t1), getSimpleName(t2)); + if (result != 0) + return result; + } + result = compareStrings(getFullyQualifiedName(o1), getFullyQualifiedName(o2)); + if (result != 0) + return result; + return compareElementTypeKinds(o1, o2); + } + }; + } + return overrideUseComparator; + } + + private Comparator indexUseComparator = null; + /** + * Returns a Comparator for index file presentations, and are sorted as follows. + * If comparing modules and/or packages then simply compare the qualified names, + * if comparing a module or a package with a type/member then compare the + * FullyQualifiedName of the module or a package with the SimpleName of the entity, + * otherwise: + * 1. compare the ElementKind ex: Module, Package, Interface etc. + * 2a. if equal and if the type is of ExecutableElement(Constructor, Methods), + * a case insensitive comparison of parameter the type signatures + * 2b. if equal, case sensitive comparison of the type signatures + * 3. finally, if equal, compare the FQNs of the entities + * @return a comparator for index file use + */ + public Comparator makeIndexUseComparator() { + if (indexUseComparator == null) { + indexUseComparator = new Utils.ElementComparator() { + /** + * Compares two elements. + * + * @param e1 - an element. + * @param e2 - an element. + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second. + */ + @Override + public int compare(Element e1, Element e2) { + int result; + // first, compare names as appropriate + if ((isModule(e1) || isPackage(e1)) && (isModule(e2) || isPackage(e2))) { + result = compareFullyQualifiedNames(e1, e2); + } else if (isModule(e1) || isPackage(e1)) { + result = compareStrings(getFullyQualifiedName(e1), getSimpleName(e2)); + } else if (isModule(e2) || isPackage(e2)) { + result = compareStrings(getSimpleName(e1), getFullyQualifiedName(e2)); + } else { + result = compareNames(e1, e2); + } + if (result != 0) { + return result; + } + // if names are the same, compare element kinds + result = compareElementTypeKinds(e1, e2); + if (result != 0) { + return result; + } + // if element kinds are the same, and are methods, + // compare the method parameters + if (hasParameters(e1)) { + List parameters1 = ((ExecutableElement)e1).getParameters(); + List parameters2 = ((ExecutableElement)e2).getParameters(); + result = compareParameters(false, parameters1, parameters2); + if (result != 0) { + return result; + } + result = compareParameters(true, parameters1, parameters2); + if (result != 0) { + return result; + } + } + // else fall back on fully qualified names + return compareFullyQualifiedNames(e1, e2); + } + }; + } + return indexUseComparator; + } + + private Comparator typeMirrorClassUseComparator = null; + /** + * Compares the FullyQualifiedNames of two TypeMirrors + * @return + */ + public Comparator makeTypeMirrorClassUseComparator() { + if (typeMirrorClassUseComparator == null) { + typeMirrorClassUseComparator = (TypeMirror type1, TypeMirror type2) -> { + String s1 = getQualifiedTypeName(type1); + String s2 = getQualifiedTypeName(type2); + return compareStrings(s1, s2); + }; + } + return typeMirrorClassUseComparator; + } + + private Comparator typeMirrorIndexUseComparator = null; + /** + * Compares the SimpleNames of TypeMirrors if equal then the + * FullyQualifiedNames of TypeMirrors. + * + * @return + */ + public Comparator makeTypeMirrorIndexUseComparator() { + if (typeMirrorIndexUseComparator == null) { + typeMirrorIndexUseComparator = (TypeMirror t1, TypeMirror t2) -> { + int result = compareStrings(getTypeName(t1, false), getTypeName(t2, false)); + if (result != 0) + return result; + return compareStrings(getQualifiedTypeName(t1), getQualifiedTypeName(t2)); + }; + } + return typeMirrorIndexUseComparator; + } + + /** + * Get the qualified type name of a TypeMiror compatible with the Element's + * getQualified name, returns the qualified name of the Reference type + * otherwise the primitive name. + * @param t the type whose name is to be obtained. + * @return the fully qualified name of Reference type or the primitive name + */ + public String getQualifiedTypeName(TypeMirror t) { + return new SimpleTypeVisitor9() { + @Override + public String visitDeclared(DeclaredType t, Void p) { + return getFullyQualifiedName(t.asElement()); + } + + @Override + public String visitArray(ArrayType t, Void p) { + return visit(t.getComponentType()); + } + + @Override + public String visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) { + // The knee jerk reaction is to do this but don't!, as we would like + // it to be compatible with the old world, now if we decide to do so + // care must be taken to avoid collisions. + // return getFullyQualifiedName(t.asElement()); + return t.toString(); + } + + @Override + protected String defaultAction(TypeMirror t, Void p) { + return t.toString(); + } + + }.visit(t); + } + + /** + * A generic utility which returns the fully qualified names of an entity, + * if the entity is not qualifiable then its enclosing entity, it is upto + * the caller to add the elements name as required. + * @param e the element to get FQN for. + * @return the name + */ + public String getFullyQualifiedName(Element e) { + return getFullyQualifiedName(e, true); + } + + public String getFullyQualifiedName(Element e, final boolean outer) { + return new SimpleElementVisitor9() { + @Override + public String visitModule(ModuleElement e, Void p) { + return e.getQualifiedName().toString(); + } + + @Override + public String visitPackage(PackageElement e, Void p) { + return e.getQualifiedName().toString(); + } + + @Override + public String visitType(TypeElement e, Void p) { + return e.getQualifiedName().toString(); + } + + @Override + protected String defaultAction(Element e, Void p) { + return outer ? visit(e.getEnclosingElement()) : e.getSimpleName().toString(); + } + }.visit(e); + } + + private Comparator classUseComparator = null; + /** + * Comparator for ClassUse presentations, and sorts as follows: + * 1. member names + * 2. then fully qualified member names + * 3. then parameter types if applicable + * 4. finally the element kinds ie. package, class, interface etc. + * @return a comparator to sort classes and members for class use + */ + public Comparator makeClassUseComparator() { + if (classUseComparator == null) { + classUseComparator = new Utils.ElementComparator() { + /** + * Compares two Elements. + * + * @param e1 - an element. + * @param e2 - an element. + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second. + */ + @Override + public int compare(Element e1, Element e2) { + int result = compareNames(e1, e2); + if (result != 0) { + return result; + } + result = compareFullyQualifiedNames(e1, e2); + if (result != 0) { + return result; + } + if (hasParameters(e1) && hasParameters(e2)) { + @SuppressWarnings("unchecked") + List parameters1 = (List)((ExecutableElement)e1).getParameters(); + @SuppressWarnings("unchecked") + List parameters2 = (List)((ExecutableElement)e2).getParameters(); + result = compareParameters(false, parameters1, parameters2); + if (result != 0) { + return result; + } + result = compareParameters(true, parameters1, parameters2); + } + if (result != 0) { + return result; + } + return compareElementTypeKinds(e1, e2); + } + }; + } + return classUseComparator; + } + + /** + * A general purpose comparator to sort Element entities, basically provides the building blocks + * for creating specific comparators for an use-case. + */ + private abstract class ElementComparator implements Comparator { + /** + * compares two parameter arrays by first comparing the length of the arrays, and + * then each Type of the parameter in the array. + * @param params1 the first parameter array. + * @param params2 the first parameter array. + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second. + */ + final EnumMap elementKindOrder; + public ElementComparator() { + elementKindOrder = new EnumMap<>(ElementKind.class); + elementKindOrder.put(ElementKind.MODULE, 0); + elementKindOrder.put(ElementKind.PACKAGE, 1); + elementKindOrder.put(ElementKind.CLASS, 2); + elementKindOrder.put(ElementKind.ENUM, 3); + elementKindOrder.put(ElementKind.ENUM_CONSTANT, 4); + elementKindOrder.put(ElementKind.INTERFACE, 5); + elementKindOrder.put(ElementKind.ANNOTATION_TYPE, 6); + elementKindOrder.put(ElementKind.FIELD, 7); + elementKindOrder.put(ElementKind.CONSTRUCTOR, 8); + elementKindOrder.put(ElementKind.METHOD, 9); + } + + protected int compareParameters(boolean caseSensitive, List params1, + List params2) { + + return compareStrings(caseSensitive, getParametersAsString(params1), + getParametersAsString(params2)); + } + + String getParametersAsString(List params) { + StringBuilder sb = new StringBuilder(); + for (VariableElement param : params) { + TypeMirror t = param.asType(); + // prefix P for primitive and R for reference types, thus items will + // be ordered lexically and correctly. + sb.append(getTypeCode(t)).append("-").append(t).append("-"); + } + return sb.toString(); + } + + private String getTypeCode(TypeMirror t) { + return new SimpleTypeVisitor9() { + + @Override + public String visitPrimitive(PrimitiveType t, Void p) { + return "P"; + } + @Override + public String visitArray(ArrayType t, Void p) { + return visit(t.getComponentType()); + } + @Override + protected String defaultAction(TypeMirror e, Void p) { + return "R"; + } + + }.visit(t); + } + + /** + * Compares two Elements, typically the name of a method, + * field or constructor. + * @param e1 the first Element. + * @param e2 the second Element. + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second. + */ + protected int compareNames(Element e1, Element e2) { + return compareStrings(getSimpleName(e1), getSimpleName(e2)); + } + + /** + * Compares the fully qualified names of the entities + * @param e1 the first Element. + * @param e2 the first Element. + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second. + */ + protected int compareFullyQualifiedNames(Element e1, Element e2) { + // add simplename to be compatible + String thisElement = getFullyQualifiedName(e1); + String thatElement = getFullyQualifiedName(e2); + return compareStrings(thisElement, thatElement); + } + protected int compareElementTypeKinds(Element e1, Element e2) { + return Integer.compare(elementKindOrder.get(e1.getKind()), + elementKindOrder.get(e2.getKind())); + } + boolean hasParameters(Element e) { + return new SimpleElementVisitor9() { + @Override + public Boolean visitExecutable(ExecutableElement e, Void p) { + return true; + } + + @Override + protected Boolean defaultAction(Element e, Void p) { + return false; + } + + }.visit(e); + } + + /** + * The fully qualified names of the entities, used solely by the comparator. + * + * @return a negative integer, zero, or a positive integer as the first argument is less + * than, equal to, or greater than the second. + */ + private String getFullyQualifiedName(Element e) { + return new SimpleElementVisitor9() { + @Override + public String visitModule(ModuleElement e, Void p) { + return e.getQualifiedName().toString(); + } + + @Override + public String visitPackage(PackageElement e, Void p) { + return e.getQualifiedName().toString(); + } + + @Override + public String visitExecutable(ExecutableElement e, Void p) { + // For backward compatibility + return getFullyQualifiedName(e.getEnclosingElement()) + + "." + e.getSimpleName().toString(); + } + + @Override + public String visitType(TypeElement e, Void p) { + return e.getQualifiedName().toString(); + } + + @Override + protected String defaultAction(Element e, Void p) { + return getEnclosingTypeElement(e).getQualifiedName().toString() + + "." + e.getSimpleName().toString(); + } + }.visit(e); + } + } + + public Iterable getEnclosedTypeElements(PackageElement pkg) { + List out = getInterfaces(pkg); + out.addAll(getClasses(pkg)); + out.addAll(getEnums(pkg)); + out.addAll(getAnnotationTypes(pkg)); + return out; + } + + // Element related methods + public List getAnnotationMembers(TypeElement aClass) { + List members = getAnnotationFields(aClass); + members.addAll(getAnnotationMethods(aClass)); + return members; + } + + public List getAnnotationFields(TypeElement aClass) { + return getItems0(aClass, true, FIELD); + } + + List getAnnotationFieldsUnfiltered(TypeElement aClass) { + return getItems0(aClass, true, FIELD); + } + + public List getAnnotationMethods(TypeElement aClass) { + return getItems0(aClass, true, METHOD); + } + + public List getAnnotationTypes(Element e) { + return convertToTypeElement(getItems(e, true, ANNOTATION_TYPE)); + } + + public List getAnnotationTypesUnfiltered(Element e) { + return convertToTypeElement(getItems(e, false, ANNOTATION_TYPE)); + } + + public List getFields(Element e) { + return convertToVariableElement(getItems(e, true, FIELD)); + } + + public List getFieldsUnfiltered(Element e) { + return convertToVariableElement(getItems(e, false, FIELD)); + } + + public List getClasses(Element e) { + return convertToTypeElement(getItems(e, true, CLASS)); + } + + public List getClassesUnfiltered(Element e) { + return convertToTypeElement(getItems(e, false, CLASS)); + } + + public List getConstructors(Element e) { + return convertToExecutableElement(getItems(e, true, CONSTRUCTOR)); + } + + public List getMethods(Element e) { + return convertToExecutableElement(getItems(e, true, METHOD)); + } + + List getMethodsUnfiltered(Element e) { + return convertToExecutableElement(getItems(e, false, METHOD)); + } + + public int getOrdinalValue(VariableElement member) { + if (member == null || member.getKind() != ENUM_CONSTANT) { + throw new IllegalArgumentException("must be an enum constant: " + member); + } + return member.getEnclosingElement().getEnclosedElements().indexOf(member); + } + + private Map> modulePackageMap = null; + public Map> getModulePackageMap() { + if (modulePackageMap == null) { + modulePackageMap = new HashMap<>(); + Set pkgs = configuration.getIncludedPackageElements(); + pkgs.forEach((pkg) -> { + ModuleElement mod = elementUtils.getModuleOf(pkg); + modulePackageMap.computeIfAbsent(mod, m -> new HashSet<>()).add(pkg); + }); + } + return modulePackageMap; + } + + public Map getDependentModules(ModuleElement mdle) { + Map result = new TreeMap<>(makeModuleComparator()); + Deque queue = new ArrayDeque<>(); + // get all the requires for the element in question + for (RequiresDirective rd : ElementFilter.requiresIn(mdle.getDirectives())) { + ModuleElement dep = rd.getDependency(); + // add the dependency to work queue + if (!result.containsKey(dep)) { + if (rd.isTransitive()) { + queue.addLast(dep); + } + } + // add all exports for the primary module + result.put(rd.getDependency(), getModifiers(rd)); + } + + // add only requires public for subsequent module dependencies + for (ModuleElement m = queue.poll(); m != null; m = queue.poll()) { + for (RequiresDirective rd : ElementFilter.requiresIn(m.getDirectives())) { + ModuleElement dep = rd.getDependency(); + if (!result.containsKey(dep)) { + if (rd.isTransitive()) { + result.put(dep, getModifiers(rd)); + queue.addLast(dep); + } + } + } + } + return result; + } + + public String getModifiers(RequiresDirective rd) { + StringBuilder modifiers = new StringBuilder(); + String sep=""; + if (rd.isTransitive()) { + modifiers.append("transitive"); + sep = " "; + } + if (rd.isStatic()) { + modifiers.append(sep); + modifiers.append("static"); + } + return (modifiers.length() == 0) ? " " : modifiers.toString(); + } + + public long getLineNumber(Element e) { + TreePath path = getTreePath(e); + if (path == null) { // maybe null if synthesized + TypeElement encl = getEnclosingTypeElement(e); + path = getTreePath(encl); + } + CompilationUnitTree cu = path.getCompilationUnit(); + LineMap lineMap = cu.getLineMap(); + DocSourcePositions spos = docTrees.getSourcePositions(); + long pos = spos.getStartPosition(cu, path.getLeaf()); + return lineMap.getLineNumber(pos); + } + + public List convertToExecutableElement(List list) { + List out = new ArrayList<>(list.size()); + for (Element e : list) { + out.add((ExecutableElement)e); + } + return out; + } + + public List convertToTypeElement(List list) { + List out = new ArrayList<>(list.size()); + for (Element e : list) { + out.add((TypeElement)e); + } + return out; + } + + public List convertToVariableElement(List list) { + List out = new ArrayList<>(list.size()); + for (Element e : list) { + out.add((VariableElement) e); + } + return out; + } + + public List getInterfaces(Element e) { + return convertToTypeElement(getItems(e, true, INTERFACE)); + } + + public List getInterfacesUnfiltered(Element e) { + return convertToTypeElement(getItems(e, false, INTERFACE)); + } + + List getNestedClasses(TypeElement e) { + List result = new ArrayList<>(); + recursiveGetItems(result, e, true, CLASS); + return result; + } + + List getNestedClassesUnfiltered(TypeElement e) { + List result = new ArrayList<>(); + recursiveGetItems(result, e, false, CLASS); + return result; + } + + public List getEnumConstants(Element e) { + return getItems(e, true, ENUM_CONSTANT); + } + + public List getEnums(Element e) { + return convertToTypeElement(getItems(e, true, ENUM)); + } + + public List getEnumsUnfiltered(Element e) { + return convertToTypeElement(getItems(e, false, ENUM)); + } + + public SortedSet getAllClassesUnfiltered(Element e) { + List clist = getClassesUnfiltered(e); + clist.addAll(getInterfacesUnfiltered(e)); + clist.addAll(getAnnotationTypesUnfiltered(e)); + SortedSet oset = new TreeSet<>(makeGeneralPurposeComparator()); + oset.addAll(clist); + return oset; + } + + private final HashMap> cachedClasses = new HashMap<>(); + /** + * Returns a list containing classes and interfaces, + * including annotation types. + * @param e Element + * @return List + */ + public SortedSet getAllClasses(Element e) { + SortedSet oset = cachedClasses.get(e); + if (oset != null) + return oset; + List clist = getClasses(e); + clist.addAll(getInterfaces(e)); + clist.addAll(getAnnotationTypes(e)); + clist.addAll(getEnums(e)); + oset = new TreeSet<>(makeGeneralPurposeComparator()); + oset.addAll(clist); + cachedClasses.put(e, oset); + return oset; + } + + /* + * Get all the elements unfiltered and filter them finally based + * on its visibility, this works differently from the other getters. + */ + private List getInnerClasses(Element e, boolean filter) { + List olist = new ArrayList<>(); + for (TypeElement te : getClassesUnfiltered(e)) { + if (!filter || configuration.docEnv.isSelected(te)) { + olist.add(te); + } + } + for (TypeElement te : getInterfacesUnfiltered(e)) { + if (!filter || configuration.docEnv.isSelected(te)) { + olist.add(te); + } + } + for (TypeElement te : getAnnotationTypesUnfiltered(e)) { + if (!filter || configuration.docEnv.isSelected(te)) { + olist.add(te); + } + } + for (TypeElement te : getEnumsUnfiltered(e)) { + if (!filter || configuration.docEnv.isSelected(te)) { + olist.add(te); + } + } + return olist; + } + + public List getInnerClasses(Element e) { + return getInnerClasses(e, true); + } + + public List getInnerClassesUnfiltered(Element e) { + return getInnerClasses(e, false); + } + + /** + * Returns a list of classes that are not errors or exceptions + * @param e Element + * @return List + */ + public List getOrdinaryClasses(Element e) { + return getClasses(e).stream() + .filter(te -> (!isException(te) && !isError(te))) + .collect(Collectors.toList()); + } + + public List getErrors(Element e) { + return getClasses(e) + .stream() + .filter(this::isError) + .collect(Collectors.toList()); + } + + public List getExceptions(Element e) { + return getClasses(e) + .stream() + .filter(this::isException) + .collect(Collectors.toList()); + } + + List getItems(Element e, boolean filter, ElementKind select) { + List elements = new ArrayList<>(); + // maintain backward compatibility by returning a null list, see AnnotationDocType.methods(). + if (configuration.backwardCompatibility && e.getKind() == ANNOTATION_TYPE) + return elements; + return new SimpleElementVisitor9, Void>() { + + @Override + public List visitPackage(PackageElement e, Void p) { + recursiveGetItems(elements, e, filter, select); + return elements; + } + + @Override + protected List defaultAction(Element e0, Void p) { + return getItems0(e0, filter, select); + } + + }.visit(e); + } + + EnumSet nestedKinds = EnumSet.of(ANNOTATION_TYPE, CLASS, ENUM, INTERFACE); + + void recursiveGetItems(Collection list, Element e, boolean filter, ElementKind... select) { + list.addAll(getItems0(e, filter, select)); + List classes = getItems0(e, filter, nestedKinds); + for (Element c : classes) { + list.addAll(getItems0(c, filter, select)); + if (isTypeElement(c)) { + recursiveGetItems(list, c, filter, select); + } + } + } + + private List getItems0(Element te, boolean filter, ElementKind... select) { + EnumSet kinds = EnumSet.copyOf(Arrays.asList(select)); + return getItems0(te, filter, kinds); + } + + private List getItems0(Element te, boolean filter, Set kinds) { + List elements = new ArrayList<>(); + for (Element e : te.getEnclosedElements()) { + if (kinds.contains(e.getKind())) { + if (!filter || shouldDocument(e)) { + elements.add(e); + } + } + } + return elements; + } + + private SimpleElementVisitor9 shouldDocumentVisitor = null; + private boolean shouldDocument(Element e) { + if (shouldDocumentVisitor == null) { + shouldDocumentVisitor = new SimpleElementVisitor9() { + private boolean hasSource(TypeElement e) { + return configuration.docEnv.getFileKind(e) == + javax.tools.JavaFileObject.Kind.SOURCE; + } + + // handle types + @Override + public Boolean visitType(TypeElement e, Void p) { + return configuration.docEnv.isSelected(e) && hasSource(e); + } + + // handle everything else + @Override + protected Boolean defaultAction(Element e, Void p) { + return configuration.docEnv.isSelected(e); + } + + @Override + public Boolean visitUnknown(Element e, Void p) { + throw new AssertionError("unkown element: " + p); + } + }; + } + return shouldDocumentVisitor.visit(e); + } + + /* + * nameCache is maintained for improving the comparator + * performance, noting that the Collator used by the comparators + * use Strings, as of this writing. + * TODO: when those APIs handle charSequences, the use of + * this nameCache must be re-investigated and removed. + */ + private final Map nameCache = new LinkedHashMap<>(); + + /** + * Returns the name of the element after the last dot of the package name. + * This emulates the behavior of the old doclet. + * @param e an element whose name is required + * @return the name + */ + public String getSimpleName(Element e) { + return nameCache.computeIfAbsent(e, this::getSimpleName0); + } + + private SimpleElementVisitor9 snvisitor = null; + + private String getSimpleName0(Element e) { + if (snvisitor == null) { + snvisitor = new SimpleElementVisitor9() { + @Override + public String visitModule(ModuleElement e, Void p) { + return e.getQualifiedName().toString(); // temp fix for 8182736 + } + + @Override + public String visitType(TypeElement e, Void p) { + StringBuilder sb = new StringBuilder(e.getSimpleName()); + Element enclosed = e.getEnclosingElement(); + while (enclosed != null + && (enclosed.getKind().isClass() || enclosed.getKind().isInterface())) { + sb.insert(0, enclosed.getSimpleName() + "."); + enclosed = enclosed.getEnclosingElement(); + } + return sb.toString(); + } + + @Override + public String visitExecutable(ExecutableElement e, Void p) { + if (e.getKind() == CONSTRUCTOR || e.getKind() == STATIC_INIT) { + return e.getEnclosingElement().getSimpleName().toString(); + } + return e.getSimpleName().toString(); + } + + @Override + protected String defaultAction(Element e, Void p) { + return e.getSimpleName().toString(); + } + }; + } + return snvisitor.visit(e); + } + + public TypeElement getEnclosingTypeElement(Element e) { + if (e.getKind() == ElementKind.PACKAGE) + return null; + Element encl = e.getEnclosingElement(); + ElementKind kind = encl.getKind(); + if (kind == ElementKind.PACKAGE) + return null; + while (!(kind.isClass() || kind.isInterface())) { + encl = encl.getEnclosingElement(); + } + return (TypeElement)encl; + } + + private ConstantValueExpression cve = null; + + public String constantValueExpresion(VariableElement ve) { + if (cve == null) + cve = new ConstantValueExpression(); + return cve.constantValueExpression(configuration.workArounds, ve); + } + + private static class ConstantValueExpression { + public String constantValueExpression(WorkArounds workArounds, VariableElement ve) { + return new TypeKindVisitor9() { + /* TODO: we need to fix this correctly. + * we have a discrepancy here, note the use of getConstValue + * vs. getConstantValue, at some point we need to use + * getConstantValue. + * In the legacy world byte and char primitives appear as Integer values, + * thus a byte value of 127 will appear as 127, but in the new world, + * a byte value appears as Byte thus 0x7f will be printed, similarly + * chars will be translated to \n, \r etc. however, in the new world, + * they will be printed as decimal values. The new world is correct, + * and we should fix this by using getConstantValue and the visitor to + * address this in the future. + */ + @Override + public String visitPrimitiveAsBoolean(PrimitiveType t, Object val) { + return (int)val == 0 ? "false" : "true"; + } + + @Override + public String visitPrimitiveAsDouble(PrimitiveType t, Object val) { + return sourceForm(((Double)val), 'd'); + } + + @Override + public String visitPrimitiveAsFloat(PrimitiveType t, Object val) { + return sourceForm(((Float)val).doubleValue(), 'f'); + } + + @Override + public String visitPrimitiveAsLong(PrimitiveType t, Object val) { + return val + "L"; + } + + @Override + protected String defaultAction(TypeMirror e, Object val) { + if (val == null) + return null; + else if (val instanceof Character) + return sourceForm(((Character)val)); + else if (val instanceof Byte) + return sourceForm(((Byte)val)); + else if (val instanceof String) + return sourceForm((String)val); + return val.toString(); // covers int, short + } + }.visit(ve.asType(), workArounds.getConstValue(ve)); + } + + // where + private String sourceForm(double v, char suffix) { + if (Double.isNaN(v)) + return "0" + suffix + "/0" + suffix; + if (v == Double.POSITIVE_INFINITY) + return "1" + suffix + "/0" + suffix; + if (v == Double.NEGATIVE_INFINITY) + return "-1" + suffix + "/0" + suffix; + return v + (suffix == 'f' || suffix == 'F' ? "" + suffix : ""); + } + + private String sourceForm(char c) { + StringBuilder buf = new StringBuilder(8); + buf.append('\''); + sourceChar(c, buf); + buf.append('\''); + return buf.toString(); + } + + private String sourceForm(byte c) { + return "0x" + Integer.toString(c & 0xff, 16); + } + + private String sourceForm(String s) { + StringBuilder buf = new StringBuilder(s.length() + 5); + buf.append('\"'); + for (int i=0; i>12))); + buf.append(chars.charAt(15 & (c>>8))); + buf.append(chars.charAt(15 & (c>>4))); + buf.append(chars.charAt(15 & (c>>0))); + } + private boolean isPrintableAscii(char c) { + return c >= ' ' && c <= '~'; + } + } + + public boolean isEnclosingPackageIncluded(TypeElement te) { + return isIncluded(containingPackage(te)); + } + + public boolean isIncluded(Element e) { + return configuration.docEnv.isIncluded(e); + } + + private SimpleElementVisitor9 specifiedVisitor = null; + public boolean isSpecified(Element e) { + if (specifiedVisitor == null) { + specifiedVisitor = new SimpleElementVisitor9() { + @Override + public Boolean visitModule(ModuleElement e, Void p) { + return configuration.getSpecifiedModuleElements().contains(e); + } + + @Override + public Boolean visitPackage(PackageElement e, Void p) { + return configuration.getSpecifiedPackageElements().contains(e); + } + + @Override + public Boolean visitType(TypeElement e, Void p) { + return configuration.getSpecifiedTypeElements().contains(e); + } + + @Override + protected Boolean defaultAction(Element e, Void p) { + return false; + } + }; + } + return specifiedVisitor.visit(e); + } + + /** + * package name, an unnamed package is returned as <Unnamed> + * @param pkg + * @return + */ + public String getPackageName(PackageElement pkg) { + if (pkg == null || pkg.isUnnamed()) { + return DocletConstants.DEFAULT_PACKAGE_NAME; + } + return pkg.getQualifiedName().toString(); + } + + public boolean isAttribute(DocTree doctree) { + return isKind(doctree, ATTRIBUTE); + } + + public boolean isAuthor(DocTree doctree) { + return isKind(doctree, AUTHOR); + } + + public boolean isComment(DocTree doctree) { + return isKind(doctree, COMMENT); + } + + public boolean isDeprecated(DocTree doctree) { + return isKind(doctree, DEPRECATED); + } + + public boolean isDocComment(DocTree doctree) { + return isKind(doctree, DOC_COMMENT); + } + + public boolean isDocRoot(DocTree doctree) { + return isKind(doctree, DOC_ROOT); + } + + public boolean isEndElement(DocTree doctree) { + return isKind(doctree, END_ELEMENT); + } + + public boolean isEntity(DocTree doctree) { + return isKind(doctree, ENTITY); + } + + public boolean isErroneous(DocTree doctree) { + return isKind(doctree, ERRONEOUS); + } + + public boolean isException(DocTree doctree) { + return isKind(doctree, EXCEPTION); + } + + public boolean isIdentifier(DocTree doctree) { + return isKind(doctree, IDENTIFIER); + } + + public boolean isInheritDoc(DocTree doctree) { + return isKind(doctree, INHERIT_DOC); + } + + public boolean isLink(DocTree doctree) { + return isKind(doctree, LINK); + } + + public boolean isLinkPlain(DocTree doctree) { + return isKind(doctree, LINK_PLAIN); + } + + public boolean isLiteral(DocTree doctree) { + return isKind(doctree, LITERAL); + } + + public boolean isOther(DocTree doctree) { + return doctree.getKind() == DocTree.Kind.OTHER; + } + + public boolean isParam(DocTree doctree) { + return isKind(doctree, PARAM); + } + + public boolean isReference(DocTree doctree) { + return isKind(doctree, REFERENCE); + } + + public boolean isReturn(DocTree doctree) { + return isKind(doctree, RETURN); + } + + public boolean isSee(DocTree doctree) { + return isKind(doctree, SEE); + } + + public boolean isSerial(DocTree doctree) { + return isKind(doctree, SERIAL); + } + + public boolean isSerialData(DocTree doctree) { + return isKind(doctree, SERIAL_DATA); + } + + public boolean isSerialField(DocTree doctree) { + return isKind(doctree, SERIAL_FIELD); + } + + public boolean isSince(DocTree doctree) { + return isKind(doctree, SINCE); + } + + public boolean isStartElement(DocTree doctree) { + return isKind(doctree, START_ELEMENT); + } + + public boolean isText(DocTree doctree) { + return isKind(doctree, TEXT); + } + + public boolean isThrows(DocTree doctree) { + return isKind(doctree, THROWS); + } + + public boolean isUnknownBlockTag(DocTree doctree) { + return isKind(doctree, UNKNOWN_BLOCK_TAG); + } + + public boolean isUnknownInlineTag(DocTree doctree) { + return isKind(doctree, UNKNOWN_INLINE_TAG); + } + + public boolean isValue(DocTree doctree) { + return isKind(doctree, VALUE); + } + + public boolean isVersion(DocTree doctree) { + return isKind(doctree, VERSION); + } + + private boolean isKind(DocTree doctree, DocTree.Kind match) { + return doctree.getKind() == match; + } + + private final WeakSoftHashMap wksMap = new WeakSoftHashMap(this); + + public CommentHelper getCommentHelper(Element element) { + return wksMap.computeIfAbsent(element); + } + + public void removeCommentHelper(Element element) { + wksMap.remove(element); + } + + public List filteredList(List dlist, DocTree.Kind... select) { + List list = new ArrayList<>(dlist.size()); + if (select == null) + return dlist; + for (DocTree dt : dlist) { + if (dt.getKind() != ERRONEOUS) { + for (DocTree.Kind kind : select) { + if (dt.getKind() == kind) { + list.add(dt); + } + } + } + } + return list; + } + + private List getBlockTags0(Element element, DocTree.Kind... kinds) { + DocCommentTree dcTree = getDocCommentTree(element); + if (dcTree == null) + return Collections.emptyList(); + + return filteredList(dcTree.getBlockTags(), kinds); + } + + public List getBlockTags(Element element) { + return getBlockTags0(element, (Kind[]) null); + } + + public List getBlockTags(Element element, DocTree.Kind... kinds) { + return getBlockTags0(element, kinds); + } + + public List getBlockTags(Element element, String tagName) { + DocTree.Kind kind = null; + switch (tagName) { + case "author": + case "deprecated": + case "hidden": + case "param": + case "return": + case "see": + case "serial": + case "since": + case "throws": + case "exception": + case "version": + kind = DocTree.Kind.valueOf(tagName.toUpperCase()); + return getBlockTags(element, kind); + case "serialData": + kind = SERIAL_DATA; + return getBlockTags(element, kind); + case "serialField": + kind = SERIAL_FIELD; + return getBlockTags(element, kind); + default: + kind = DocTree.Kind.UNKNOWN_BLOCK_TAG; + break; + } + List blockTags = getBlockTags(element, kind); + List out = new ArrayList<>(); + String tname = tagName.startsWith("@") ? tagName.substring(1) : tagName; + CommentHelper ch = getCommentHelper(element); + for (DocTree dt : blockTags) { + if (ch.getTagName(dt).equals(tname)) { + out.add(dt); + } + } + return out; + } + + public boolean hasBlockTag(Element element, DocTree.Kind kind) { + return hasBlockTag(element, kind, null); + } + + public boolean hasBlockTag(Element element, DocTree.Kind kind, final String tagName) { + CommentHelper ch = getCommentHelper(element); + String tname = tagName != null && tagName.startsWith("@") + ? tagName.substring(1) + : tagName; + for (DocTree dt : getBlockTags(element, kind)) { + if (dt.getKind() == kind) { + if (tname == null || ch.getTagName(dt).equals(tname)) { + return true; + } + } + } + return false; + } + + /** + * Gets a TreePath for an Element. Note this method is called very + * frequently, care must be taken to ensure this method is lithe + * and efficient. + * @param e an Element + * @return TreePath + */ + public TreePath getTreePath(Element e) { + DocCommentDuo duo = dcTreeCache.get(e); + if (isValidDuo(duo) && duo.treePath != null) { + return duo.treePath; + } + duo = configuration.cmtUtils.getSyntheticCommentDuo(e); + if (isValidDuo(duo) && duo.treePath != null) { + return duo.treePath; + } + Map elementToTreePath = configuration.workArounds.getElementToTreePath(); + TreePath path = elementToTreePath.get(e); + if (path != null || elementToTreePath.containsKey(e)) { + // expedite the path and one that is a null + return path; + } + return elementToTreePath.computeIfAbsent(e, docTrees::getPath); + } + + private final Map dcTreeCache = new LinkedHashMap<>(); + + /** + * Retrieves the doc comments for a given element. + * @param element + * @return DocCommentTree for the Element + */ + public DocCommentTree getDocCommentTree0(Element element) { + + DocCommentDuo duo = null; + + ElementKind kind = element.getKind(); + if (kind == ElementKind.PACKAGE || kind == ElementKind.OTHER) { + duo = dcTreeCache.get(element); // local cache + if (!isValidDuo(duo) && kind == ElementKind.PACKAGE) { + // package-info.java + duo = getDocCommentTuple(element); + } + if (!isValidDuo(duo)) { + // package.html or overview.html + duo = configuration.cmtUtils.getHtmlCommentDuo(element); // html source + } + } else { + duo = configuration.cmtUtils.getSyntheticCommentDuo(element); + if (!isValidDuo(duo)) { + duo = dcTreeCache.get(element); // local cache + } + if (!isValidDuo(duo)) { + duo = getDocCommentTuple(element); // get the real mccoy + } + } + + DocCommentTree docCommentTree = isValidDuo(duo) ? duo.dcTree : null; + TreePath path = isValidDuo(duo) ? duo.treePath : null; + if (!dcTreeCache.containsKey(element)) { + if (docCommentTree != null && path != null) { + if (!configuration.isAllowScriptInComments()) { + try { + javaScriptScanner.scan(docCommentTree, path, p -> { + throw new JavaScriptScanner.Fault(); + }); + } catch (JavaScriptScanner.Fault jsf) { + String text = configuration.getText("doclet.JavaScript_in_comment"); + throw new UncheckedDocletException(new SimpleDocletException(text, jsf)); + } + } + configuration.workArounds.runDocLint(path); + } + dcTreeCache.put(element, duo); + } + return docCommentTree; + } + + private DocCommentDuo getDocCommentTuple(Element element) { + // prevent nasty things downstream with overview element + if (element.getKind() != ElementKind.OTHER) { + TreePath path = getTreePath(element); + if (path != null) { + DocCommentTree docCommentTree = docTrees.getDocCommentTree(path); + return new DocCommentDuo(path, docCommentTree); + } + } + return null; + } + + public void checkJavaScriptInOption(String name, String value) { + if (!configuration.isAllowScriptInComments()) { + DocCommentTree dct = configuration.cmtUtils.parse( + URI.create("option://" + name.replace("-", "")), "" + value + ""); + try { + javaScriptScanner.scan(dct, null, p -> { + throw new JavaScriptScanner.Fault(); + }); + } catch (JavaScriptScanner.Fault jsf) { + String text = configuration.getText("doclet.JavaScript_in_option", name); + throw new UncheckedDocletException(new SimpleDocletException(text, jsf)); + } + } + } + + boolean isValidDuo(DocCommentDuo duo) { + return duo != null && duo.dcTree != null; + } + + public DocCommentTree getDocCommentTree(Element element) { + CommentHelper ch = wksMap.get(element); + if (ch != null) { + return ch.dctree; + } + DocCommentTree dcTree = getDocCommentTree0(element); + if (dcTree != null) { + wksMap.put(element, new CommentHelper(configuration, element, getTreePath(element), dcTree)); + } + return dcTree; + } + + public List getFullBody(Element element) { + DocCommentTree docCommentTree = getDocCommentTree(element); + return (docCommentTree == null) + ? Collections.emptyList() + : docCommentTree.getFullBody(); + } + + public List getBody(Element element) { + DocCommentTree docCommentTree = getDocCommentTree(element); + return (docCommentTree == null) + ? Collections.emptyList() + : docCommentTree.getFullBody(); + } + + public List getDeprecatedTrees(Element element) { + return getBlockTags(element, DEPRECATED); + } + + public List getProvidesTrees(Element element) { + return getBlockTags(element, PROVIDES); + } + + public List getSeeTrees(Element element) { + return getBlockTags(element, SEE); + } + + public List getSerialTrees(Element element) { + return getBlockTags(element, SERIAL); + } + + public List getSerialFieldTrees(VariableElement field) { + return getBlockTags(field, DocTree.Kind.SERIAL_FIELD); + } + + public List getThrowsTrees(Element element) { + return getBlockTags(element, DocTree.Kind.EXCEPTION, DocTree.Kind.THROWS); + } + + public List getTypeParamTrees(Element element) { + return getParamTrees(element, true); + } + + public List getParamTrees(Element element) { + return getParamTrees(element, false); + } + + private List getParamTrees(Element element, boolean isTypeParameters) { + List out = new ArrayList<>(); + for (DocTree dt : getBlockTags(element, PARAM)) { + ParamTree pt = (ParamTree) dt; + if (pt.isTypeParameter() == isTypeParameters) { + out.add(dt); + } + } + return out; + } + + public List getReturnTrees(Element element) { + List out = new ArrayList<>(); + for (DocTree dt : getBlockTags(element, RETURN)) { + out.add(dt); + } + return out; + } + + public List getUsesTrees(Element element) { + return getBlockTags(element, USES); + } + + public List getFirstSentenceTrees(Element element) { + DocCommentTree dcTree = getDocCommentTree(element); + if (dcTree == null) { + return Collections.emptyList(); + } + List out = new ArrayList<>(); + for (DocTree dt : dcTree.getFirstSentence()) { + out.add(dt); + } + return out; + } + + public ModuleElement containingModule(Element e) { + return elementUtils.getModuleOf(e); + } + + public PackageElement containingPackage(Element e) { + return elementUtils.getPackageOf(e); + } + + public TypeElement getTopMostContainingTypeElement(Element e) { + if (isPackage(e)) { + return null; + } + TypeElement outer = getEnclosingTypeElement(e); + if (outer == null) + return (TypeElement)e; + while (outer != null && outer.getNestingKind().isNested()) { + outer = getEnclosingTypeElement(outer); + } + return outer; + } + + static class WeakSoftHashMap implements Map { + + private final WeakHashMap> wkMap; + private final Utils utils; + public WeakSoftHashMap(Utils utils) { + wkMap = new WeakHashMap<>(); + this.utils = utils; + } + + @Override + public boolean containsKey(Object key) { + return wkMap.containsKey(key); + } + + @Override + public Collection values() { + Set out = new LinkedHashSet<>(); + for (SoftReference v : wkMap.values()) { + out.add(v.get()); + } + return out; + } + + @Override + public boolean containsValue(Object value) { + return wkMap.containsValue(new SoftReference<>((CommentHelper)value)); + } + + @Override + public CommentHelper remove(Object key) { + SoftReference value = wkMap.remove(key); + return value == null ? null : value.get(); + } + + + @Override + public CommentHelper put(Element key, CommentHelper value) { + SoftReference nvalue = wkMap.put(key, new SoftReference<>(value)); + return nvalue == null ? null : nvalue.get(); + } + + @Override + public CommentHelper get(Object key) { + SoftReference value = wkMap.get(key); + return value == null ? null : value.get(); + } + + @Override + public int size() { + return wkMap.size(); + } + + @Override + public boolean isEmpty() { + return wkMap.isEmpty(); + } + + @Override + public void clear() { + wkMap.clear(); + } + + public CommentHelper computeIfAbsent(Element key) { + if (wkMap.containsKey(key)) { + SoftReference value = wkMap.get(key); + if (value != null) { + CommentHelper cvalue = value.get(); + if (cvalue != null) { + return cvalue; + } + } + } + CommentHelper newValue = new CommentHelper(utils.configuration, key, utils.getTreePath(key), + utils.getDocCommentTree(key)); + wkMap.put(key, new SoftReference<>(newValue)); + return newValue; + } + + + @Override + public void putAll(Map map) { + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public Set keySet() { + return wkMap.keySet(); + } + + @Override + public Set> entrySet() { + Set> out = new LinkedHashSet<>(); + for (Element e : wkMap.keySet()) { + SimpleEntry n = new SimpleEntry<>(e, get(e)); + out.add(n); + } + return out; + } + } + + /** + * A simple pair container. + * @param first a value + * @param second another value + */ + public static class Pair { + public final K first; + public final L second; + + public Pair(K first, L second) { + this.first = first; + this.second = second; + } + } +}