1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.doclava.javadoc; 18 19 import java.util.ArrayDeque; 20 import java.util.Deque; 21 import java.util.HashMap; 22 import java.util.Iterator; 23 import java.util.NoSuchElementException; 24 import javax.lang.model.element.Element; 25 import javax.lang.model.element.ElementKind; 26 import javax.lang.model.element.TypeElement; 27 import javax.lang.model.type.TypeMirror; 28 import javax.lang.model.util.Elements; 29 import javax.lang.model.util.Types; 30 import jdk.javadoc.doclet.DocletEnvironment; 31 32 class DocletElementUtils { 33 34 private final DocletEnvironment env; 35 private final Types typeUtils; 36 private final Elements elementUtils; 37 38 private final TypeMirror exceptionType; 39 private final TypeMirror errorType; 40 private final TypeMirror throwableType; 41 DocletElementUtils()42 private DocletElementUtils() { 43 throw new UnsupportedOperationException("Hide default constructor."); 44 } 45 DocletElementUtils(DocletEnvironment e)46 public DocletElementUtils(DocletEnvironment e) { 47 this.env = e; 48 this.typeUtils = e.getTypeUtils(); 49 this.elementUtils = e.getElementUtils(); 50 51 this.exceptionType = getTypeByName("java.lang.Exception"); 52 this.errorType = getTypeByName("java.lang.Error"); 53 this.throwableType = getTypeByName("java.lang.Throwable"); 54 } 55 isException(Element e)56 public boolean isException(Element e) { 57 if (!isClass(e)) { 58 return false; 59 } 60 return typeUtils.isSubtype(e.asType(), exceptionType); 61 } 62 isError(Element e)63 public boolean isError(Element e) { 64 if (!isClass(e)) { 65 return false; 66 } 67 return typeUtils.isSubtype(e.asType(), errorType); 68 } 69 isThrowable(Element e)70 public boolean isThrowable(Element e) { 71 if (!isClass(e)) { 72 return false; 73 } 74 return typeUtils.isSubtype(e.asType(), throwableType); 75 } 76 isAnnotation(Element e)77 public boolean isAnnotation(Element e) { 78 return e.getKind() == ElementKind.ANNOTATION_TYPE; 79 } 80 isEnum(Element e)81 public boolean isEnum(Element e) { 82 return e.getKind() == ElementKind.ENUM; 83 } 84 isInterface(Element e)85 public boolean isInterface(Element e) { 86 return e.getKind() == ElementKind.INTERFACE; 87 } 88 isClass(Element e)89 public boolean isClass(Element e) { 90 return e.getKind() == ElementKind.CLASS; 91 } 92 isPackage(Element e)93 public boolean isPackage(Element e) { 94 return e.getKind() == ElementKind.PACKAGE; 95 } 96 97 private final HashMap<String, TypeMirror> typeMirrorsCache = new HashMap<>(); 98 getTypeByName(String name)99 public TypeMirror getTypeByName(String name) { 100 TypeMirror mirror = typeMirrorsCache.get(name); 101 if (mirror != null) { 102 return mirror; 103 } 104 TypeElement e = elementUtils.getTypeElement(name); 105 if (e == null || (mirror = e.asType()) == null) { 106 return null; 107 } 108 typeMirrorsCache.put(name, mirror); 109 return mirror; 110 } 111 112 /** 113 * Return the class name without package qualifier but with enclosing class qualifier, e.g. for 114 * {@code java.lang.String} return {@code String} (public class); and for {@code 115 * java.util.Map.Entry} return {@code Map.Entry} (nested class). 116 * 117 * @param e class 118 * @return class name without package qualifier but with enclosing class qualifier 119 */ getClassNameUntilNotNested(TypeElement e)120 public String getClassNameUntilNotNested(TypeElement e) { 121 Deque<String> acc = new ArrayDeque<>(); 122 var it = new EnclosingUntilNotNestedIterator(e, this); 123 it.forEachRemaining(nestedElement -> acc.addFirst(nestedElement.getSimpleName().toString())); 124 return String.join(".", acc); 125 } 126 getEnclosingTypeElement(Element e)127 public TypeElement getEnclosingTypeElement(Element e) { 128 if (isPackage(e)) { 129 return null; 130 131 } 132 Element encl = e.getEnclosingElement(); 133 if (isPackage(encl)) { 134 return null; 135 } 136 while (!(isClass(encl) || isEnum(encl) || isInterface(encl) || isAnnotation(encl))) { 137 encl = encl.getEnclosingElement(); 138 if (encl == null) { 139 return null; 140 } 141 } 142 return (TypeElement) encl; 143 } 144 145 /** 146 * Iterates over the immediately lexically enclosing elements for a nested type up until type 147 * is no longer nested. 148 */ 149 private static class EnclosingUntilNotNestedIterator implements Iterator<TypeElement> { 150 151 private TypeElement current; 152 private final DocletElementUtils utils; 153 EnclosingUntilNotNestedIterator(TypeElement typeElement, DocletElementUtils utils)154 public EnclosingUntilNotNestedIterator(TypeElement typeElement, DocletElementUtils utils) { 155 this.current = typeElement; 156 this.utils = utils; 157 } 158 159 @Override hasNext()160 public boolean hasNext() { 161 return (current != null); 162 } 163 164 @Override next()165 public TypeElement next() { 166 if (!hasNext()) { 167 throw new NoSuchElementException(); 168 } 169 TypeElement ret = current; 170 current = utils.getEnclosingTypeElement(current); 171 return ret; 172 } 173 } 174 } 175