001/* 002 * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclint; 027 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collections; 031import java.util.EnumSet; 032import java.util.HashMap; 033import java.util.HashSet; 034import java.util.LinkedHashSet; 035import java.util.List; 036import java.util.Map; 037import java.util.Set; 038import java.util.function.Function; 039import java.util.regex.Pattern; 040import java.util.stream.Collectors; 041 042import javax.lang.model.element.AnnotationMirror; 043import javax.lang.model.element.AnnotationValue; 044import javax.lang.model.element.Element; 045import javax.lang.model.element.ElementKind; 046import javax.lang.model.element.ExecutableElement; 047import javax.lang.model.element.Modifier; 048import javax.lang.model.type.DeclaredType; 049import javax.lang.model.type.TypeKind; 050import javax.lang.model.type.TypeMirror; 051import javax.lang.model.util.Elements; 052import javax.lang.model.util.Types; 053 054import com.sun.source.doctree.DocCommentTree; 055import com.sun.source.tree.CompilationUnitTree; 056import com.sun.source.util.DocTrees; 057import com.sun.source.util.JavacTask; 058import com.sun.source.util.SourcePositions; 059import com.sun.source.util.TreePath; 060import com.sun.tools.javac.model.JavacTypes; 061import com.sun.tools.javac.tree.JCTree; 062import com.sun.tools.javac.util.MatchingUtils; 063import com.sun.tools.javac.util.StringUtils; 064 065/** 066 * Utility container for current execution environment, 067 * providing the current declaration and its doc comment. 068 */ 069public class Env { 070 /** 071 * Access kinds for declarations. 072 */ 073 public enum AccessKind { 074 PRIVATE, 075 PACKAGE, 076 PROTECTED, 077 PUBLIC; 078 079 static boolean accepts(String opt) { 080 for (AccessKind g : values()) 081 if (opt.equals(StringUtils.toLowerCase(g.name()))) 082 return true; 083 return false; 084 } 085 086 static AccessKind of(Set<Modifier> mods) { 087 if (mods.contains(Modifier.PUBLIC)) 088 return AccessKind.PUBLIC; 089 else if (mods.contains(Modifier.PROTECTED)) 090 return AccessKind.PROTECTED; 091 else if (mods.contains(Modifier.PRIVATE)) 092 return AccessKind.PRIVATE; 093 else 094 return AccessKind.PACKAGE; 095 } 096 } 097 098 /** Message handler. */ 099 final Messages messages; 100 101 Set<String> customTags; 102 103 Set<Pattern> includePackages; 104 Set<Pattern> excludePackages; 105 106 /** 107 * How to handle bad references. 108 * 109 * If {@code false}, a reference into a module that is not 110 * in the module graph will just be reported as a warning. 111 * All other bad references will be reported as errors. 112 * This is the desired behavior for javac. 113 * 114 * If {@code true}, all bad references will be reported as 115 * errors. This is the desired behavior for javadoc. 116 * 117 */ 118 boolean strictReferenceChecks = false; 119 120 // Utility classes 121 DocTrees trees; 122 Elements elements; 123 Types types; 124 125 // Types used when analysing doc comments. 126 TypeMirror java_lang_Error; 127 TypeMirror java_lang_RuntimeException; 128 TypeMirror java_lang_SuppressWarnings; 129 TypeMirror java_lang_Throwable; 130 TypeMirror java_lang_Void; 131 132 /** The path for the declaration containing the comment currently being analyzed. */ 133 TreePath currPath; 134 /** The element for the declaration containing the comment currently being analyzed. */ 135 Element currElement; 136 /** The comment current being analyzed. */ 137 DocCommentTree currDocComment; 138 /** 139 * The access kind of the declaration containing the comment currently being analyzed. 140 * This is the minimum (most restrictive) access kind of the declaration itself 141 * and that of its containers. For example, a public method in a private class is 142 * noted as private. 143 */ 144 AccessKind currAccess; 145 /** The set of methods, if any, that the current declaration overrides. */ 146 Set<? extends ExecutableElement> currOverriddenMethods; 147 148 /** A map containing the info derived from {@code @SuppressWarnings} for an element. */ 149 Map<Element, Set<Messages.Group>> suppressWarnings = new HashMap<>(); 150 151 Env() { 152 messages = new Messages(this); 153 } 154 155 void init(JavacTask task) { 156 init(DocTrees.instance(task), task.getElements(), task.getTypes()); 157 } 158 159 void init(DocTrees trees, Elements elements, Types types) { 160 this.trees = trees; 161 this.elements = elements; 162 this.types = types; 163 } 164 165 void initTypes() { 166 if (java_lang_Error != null) 167 return; 168 169 java_lang_Error = elements.getTypeElement("java.lang.Error").asType(); 170 java_lang_RuntimeException 171 = elements.getTypeElement("java.lang.RuntimeException").asType(); 172 java_lang_SuppressWarnings 173 = elements.getTypeElement("java.lang.SuppressWarnings").asType(); 174 java_lang_Throwable 175 = elements.getTypeElement("java.lang.Throwable").asType(); 176 java_lang_Void = elements.getTypeElement("java.lang.Void").asType(); 177 } 178 179 void setCustomTags(String cTags) { 180 customTags = new LinkedHashSet<>(); 181 for (String s : cTags.split(DocLint.SEPARATOR)) { 182 if (!s.isEmpty()) 183 customTags.add(s); 184 } 185 } 186 187 void setCheckPackages(String packages) { 188 includePackages = new HashSet<>(); 189 excludePackages = new HashSet<>(); 190 for (String pack : packages.split(DocLint.SEPARATOR)) { 191 boolean excluded = false; 192 if (pack.startsWith("-")) { 193 pack = pack.substring(1); 194 excluded = true; 195 } 196 if (pack.isEmpty()) 197 continue; 198 Pattern pattern = MatchingUtils.validImportStringToPattern(pack); 199 if (excluded) { 200 excludePackages.add(pattern); 201 } else { 202 includePackages.add(pattern); 203 } 204 } 205 } 206 207 static boolean validatePackages(String packages) { 208 for (String pack : packages.split(DocLint.SEPARATOR)) { 209 if (pack.startsWith("-")) { 210 pack = pack.substring(1); 211 } 212 if (!pack.isEmpty() && !MatchingUtils.isValidImportString(pack)) 213 return false; 214 } 215 return true; 216 } 217 218 /** Set the current declaration and its doc comment. */ 219 void setCurrent(TreePath path, DocCommentTree comment) { 220 currPath = path; 221 currDocComment = comment; 222 currElement = trees.getElement(currPath); 223 currOverriddenMethods 224 = ((JavacTypes) types).getOverriddenMethods(currElement); 225 226 AccessKind ak = AccessKind.PUBLIC; 227 for (TreePath p = path; p != null; p = p.getParentPath()) { 228 Element e = trees.getElement(p); 229 if (e != null && e.getKind() != ElementKind.PACKAGE 230 && e.getKind() != ElementKind.MODULE) { 231 ak = min(ak, AccessKind.of(e.getModifiers())); 232 } 233 } 234 currAccess = ak; 235 } 236 237 AccessKind getAccessKind() { 238 return currAccess; 239 } 240 241 long getPos(TreePath p) { 242 return ((JCTree) p.getLeaf()).pos; 243 } 244 245 long getStartPos(TreePath p) { 246 SourcePositions sp = trees.getSourcePositions(); 247 return sp.getStartPosition(p.getCompilationUnit(), p.getLeaf()); 248 } 249 250 boolean shouldCheck(CompilationUnitTree unit) { 251 if (includePackages == null) 252 return true; 253 254 String packageName = unit.getPackageName() != null 255 ? unit.getPackageName().toString() 256 : ""; 257 258 if (!includePackages.isEmpty()) { 259 boolean included = false; 260 for (Pattern pack : includePackages) { 261 if (pack.matcher(packageName).matches()) { 262 included = true; 263 break; 264 } 265 } 266 if (!included) 267 return false; 268 } 269 270 for (Pattern pack : excludePackages) { 271 if (pack.matcher(packageName).matches()) { 272 return false; 273 } 274 } 275 276 return true; 277 } 278 279 /** 280 * {@return whether or not warnings in a group are suppressed for the current element} 281 * @param g the group 282 */ 283 boolean suppressWarnings(Messages.Group g) { 284 return suppressWarnings(currElement, g); 285 } 286 287 /** 288 * {@return whether or not warnings in a group are suppressed for a given element} 289 * @param e the element 290 * @param g the group 291 */ 292 boolean suppressWarnings(Element e, Messages.Group g) { 293 // check if warnings are suppressed in any enclosing classes 294 Element encl = e.getEnclosingElement(); 295 if (encl != null && encl.asType().getKind() == TypeKind.DECLARED) { 296 if (suppressWarnings(encl, g)) { 297 return true; 298 } 299 } 300 301 // check the local @SuppressWarnings annotation, caching the results 302 return suppressWarnings.computeIfAbsent(e, this::getSuppressedGroups) 303 .contains(g); 304 } 305 306 /** 307 * Returns the set of groups for an element for which messages should be suppressed. 308 * The set is determined by examining the arguments for any {@code @SuppressWarnings} 309 * annotation that may be present on the element. 310 * The supported strings are: "doclint" and "doclint:GROUP,..." for each GROUP 311 * 312 * @param e the element 313 * @return the set 314 */ 315 private Set<Messages.Group> getSuppressedGroups(Element e) { 316 var gMap = Arrays.stream(Messages.Group.values()) 317 .collect( 318 Collectors.toMap(Messages.Group::optName, Function.identity())); 319 var set = EnumSet.noneOf(Messages.Group.class); 320 for (String arg : getSuppressWarningsValue(e)) { 321 if (arg.equals("doclint")) { 322 set = EnumSet.allOf(Messages.Group.class); 323 break; 324 } else if (arg.startsWith("doclint:")) { 325 final int len = "doclint:".length(); 326 for (String a : arg.substring(len).split(",")) { 327 Messages.Group argGroup = gMap.get(a); 328 if (argGroup != null) { 329 set.add(argGroup); 330 } 331 } 332 } 333 } 334 return set; 335 } 336 337 /** 338 * Returns the list of values given to an instance of {@code @SuppressWarnings} for an element, 339 * or an empty list if there is no annotation. 340 * 341 * @param e the element 342 * @return the list 343 */ 344 private List<String> getSuppressWarningsValue(Element e) { 345 for (AnnotationMirror am : e.getAnnotationMirrors()) { 346 DeclaredType dt = am.getAnnotationType(); 347 if (types.isSameType(dt, java_lang_SuppressWarnings)) { 348 var values = am.getElementValues(); 349 for (var entry : values.entrySet()) { 350 if (entry.getKey().getSimpleName().contentEquals("value")) { 351 AnnotationValue av = entry.getValue(); 352 if (av.getValue() instanceof List<?> list) { 353 List<String> result = new ArrayList<>(); 354 for (var item : list) { 355 if (item instanceof AnnotationValue avItem 356 && avItem.getValue() instanceof String s) { 357 result.add(s); 358 } 359 } 360 return result; 361 } 362 } 363 } 364 365 } 366 } 367 return List.of(); 368 } 369 370 private <T extends Comparable<T>> T min(T item1, T item2) { 371 return (item1 == null) ? item2 372 : (item2 == null) ? item1 373 : item1.compareTo(item2) <= 0 ? item1 : item2; 374 } 375}