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.io.PrintWriter; 029import java.text.MessageFormat; 030import java.util.Comparator; 031import java.util.HashMap; 032import java.util.Locale; 033import java.util.Map; 034import java.util.MissingResourceException; 035import java.util.ResourceBundle; 036import java.util.Set; 037import java.util.TreeMap; 038import java.util.TreeSet; 039 040import javax.tools.Diagnostic; 041 042import org.jdrupes.mdoclet.internal.doclint.Env.AccessKind; 043 044import com.sun.source.doctree.DocTree; 045import com.sun.source.tree.Tree; 046import com.sun.tools.javac.util.StringUtils; 047 048/** 049 * Message reporting for DocLint. 050 * 051 * Options are used to filter out messages based on group and access level. 052 * Support can be enabled for accumulating statistics of different kinds of 053 * messages. 054 */ 055public class Messages { 056 /** 057 * Groups used to categorize messages, so that messages in each group 058 * can be enabled or disabled via options. 059 */ 060 public enum Group { 061 ACCESSIBILITY, 062 HTML, 063 MISSING, 064 SYNTAX, 065 REFERENCE; 066 067 String optName() { 068 return StringUtils.toLowerCase(name()); 069 } 070 071 String notOptName() { 072 return "-" + optName(); 073 } 074 075 static boolean accepts(String opt) { 076 for (Group g : values()) 077 if (opt.equals(g.optName())) 078 return true; 079 return false; 080 } 081 } 082 083 private final Options options; 084 private final Stats stats; 085 086 ResourceBundle bundle; 087 Env env; 088 089 Messages(Env env) { 090 this.env = env; 091 String name = getClass().getPackage().getName() + ".resources.doclint"; 092 bundle = ResourceBundle.getBundle(name, Locale.ENGLISH); 093 094 stats = new Stats(bundle); 095 options = new Options(stats); 096 } 097 098 void error(Group group, DocTree tree, String code, Object... args) { 099 report(group, Diagnostic.Kind.ERROR, tree, code, args); 100 } 101 102 void warning(Group group, DocTree tree, String code, Object... args) { 103 report(group, Diagnostic.Kind.WARNING, tree, code, args); 104 } 105 106 void setOptions(String opts) { 107 options.setOptions(opts); 108 } 109 110 void setStatsEnabled(boolean b) { 111 stats.setEnabled(b); 112 } 113 114 boolean isEnabled(Group group, Env.AccessKind ak) { 115 return options.isEnabled(group, ak); 116 } 117 118 void reportStats(PrintWriter out) { 119 stats.report(out); 120 } 121 122 protected void report(Group group, Diagnostic.Kind dkind, DocTree tree, 123 String code, Object... args) { 124 if (options.isEnabled(group, env.currAccess)) { 125 if (dkind == Diagnostic.Kind.WARNING 126 && env.suppressWarnings(group)) { 127 return; 128 } 129 String msg 130 = (code == null) ? (String) args[0] : localize(code, args); 131 env.trees.printMessage(dkind, msg, tree, 132 env.currDocComment, env.currPath.getCompilationUnit()); 133 134 stats.record(group, dkind, code); 135 } 136 } 137 138 protected void report(Group group, Diagnostic.Kind dkind, Tree tree, 139 String code, Object... args) { 140 if (options.isEnabled(group, env.currAccess)) { 141 if (dkind == Diagnostic.Kind.WARNING 142 && env.suppressWarnings(group)) { 143 return; 144 } 145 String msg = localize(code, args); 146 env.trees.printMessage(dkind, msg, tree, 147 env.currPath.getCompilationUnit()); 148 149 stats.record(group, dkind, code); 150 } 151 } 152 153 String localize(String code, Object... args) { 154 String msg = bundle.getString(code); 155 if (msg == null) { 156 StringBuilder sb = new StringBuilder(); 157 sb.append("message file broken: code=").append(code); 158 if (args.length > 0) { 159 sb.append(" arguments={0}"); 160 for (int i = 1; i < args.length; i++) { 161 sb.append(", {").append(i).append("}"); 162 } 163 } 164 msg = sb.toString(); 165 } 166 return MessageFormat.format(msg, args); 167 } 168 169 // <editor-fold defaultstate="collapsed" desc="Options"> 170 171 /** 172 * Handler for (sub)options specific to message handling. 173 */ 174 static class Options { 175 Map<String, Env.AccessKind> map = new HashMap<>(); 176 private final Stats stats; 177 178 static boolean isValidOptions(String opts) { 179 for (String opt : opts.split(",")) { 180 if (!isValidOption(StringUtils.toLowerCase(opt.trim()))) 181 return false; 182 } 183 return true; 184 } 185 186 private static boolean isValidOption(String opt) { 187 if (opt.equals("none") || opt.equals(Stats.OPT)) 188 return true; 189 190 int begin = opt.startsWith("-") ? 1 : 0; 191 int sep = opt.indexOf("/"); 192 String grp = opt.substring(begin, (sep != -1) ? sep : opt.length()); 193 return ((begin == 0 && grp.equals("all")) || Group.accepts(grp)) 194 && ((sep == -1) || AccessKind.accepts(opt.substring(sep + 1))); 195 } 196 197 Options(Stats stats) { 198 this.stats = stats; 199 } 200 201 /** Determine if a message group is enabled for a particular access level. */ 202 boolean isEnabled(Group g, Env.AccessKind access) { 203 if (map.isEmpty()) 204 map.put("all", Env.AccessKind.PROTECTED); 205 206 Env.AccessKind ak = map.get(g.optName()); 207 if (ak != null && access.compareTo(ak) >= 0) 208 return true; 209 210 ak = map.get(ALL); 211 if (ak != null && access.compareTo(ak) >= 0) { 212 ak = map.get(g.notOptName()); 213 if (ak == null || access.compareTo(ak) > 0) // note >, not >= 214 return true; 215 } 216 217 return false; 218 } 219 220 void setOptions(String opts) { 221 if (opts == null) 222 setOption(ALL, Env.AccessKind.PRIVATE); 223 else { 224 for (String opt : opts.split(",")) 225 setOption(StringUtils.toLowerCase(opt.trim())); 226 } 227 } 228 229 private void setOption(String arg) throws IllegalArgumentException { 230 if (arg.equals(Stats.OPT)) { 231 stats.setEnabled(true); 232 return; 233 } 234 235 int sep = arg.indexOf("/"); 236 if (sep > 0) { 237 Env.AccessKind ak = Env.AccessKind 238 .valueOf(StringUtils.toUpperCase(arg.substring(sep + 1))); 239 setOption(arg.substring(0, sep), ak); 240 } else { 241 setOption(arg, null); 242 } 243 } 244 245 private void setOption(String opt, Env.AccessKind ak) { 246 map.put(opt, (ak != null) ? ak 247 : opt.startsWith("-") ? Env.AccessKind.PUBLIC 248 : Env.AccessKind.PRIVATE); 249 } 250 251 private static final String ALL = "all"; 252 } 253 254 // </editor-fold> 255 256 // <editor-fold defaultstate="collapsed" desc="Statistics"> 257 258 /** 259 * Optionally record statistics of different kinds of message. 260 */ 261 static class Stats { 262 public static final String OPT = "stats"; 263 public static final String NO_CODE = ""; 264 final ResourceBundle bundle; 265 266 // tables only initialized if enabled 267 int[] groupCounts; 268 int[] dkindCounts; 269 Map<String, Integer> codeCounts; 270 271 Stats(ResourceBundle bundle) { 272 this.bundle = bundle; 273 } 274 275 void setEnabled(boolean b) { 276 if (b) { 277 groupCounts = new int[Messages.Group.values().length]; 278 dkindCounts = new int[Diagnostic.Kind.values().length]; 279 codeCounts = new HashMap<>(); 280 } else { 281 groupCounts = null; 282 dkindCounts = null; 283 codeCounts = null; 284 } 285 } 286 287 void record(Messages.Group g, Diagnostic.Kind dkind, String code) { 288 if (codeCounts == null) { 289 return; 290 } 291 groupCounts[g.ordinal()]++; 292 dkindCounts[dkind.ordinal()]++; 293 if (code == null) { 294 code = NO_CODE; 295 } 296 Integer i = codeCounts.get(code); 297 codeCounts.put(code, (i == null) ? 1 : i + 1); 298 } 299 300 void report(PrintWriter out) { 301 if (codeCounts == null) { 302 return; 303 } 304 out.println("By group..."); 305 Table groupTable = new Table(); 306 for (Messages.Group g : Messages.Group.values()) { 307 groupTable.put(g.optName(), groupCounts[g.ordinal()]); 308 } 309 groupTable.print(out); 310 out.println(); 311 out.println("By diagnostic kind..."); 312 Table dkindTable = new Table(); 313 for (Diagnostic.Kind k : Diagnostic.Kind.values()) { 314 dkindTable.put(StringUtils.toLowerCase(k.toString()), 315 dkindCounts[k.ordinal()]); 316 } 317 dkindTable.print(out); 318 out.println(); 319 out.println("By message kind..."); 320 Table codeTable = new Table(); 321 for (Map.Entry<String, Integer> e : codeCounts.entrySet()) { 322 String code = e.getKey(); 323 String msg; 324 try { 325 msg = code.equals(NO_CODE) ? "OTHER" 326 : bundle.getString(code); 327 } catch (MissingResourceException ex) { 328 msg = code; 329 } 330 codeTable.put(msg, e.getValue()); 331 } 332 codeTable.print(out); 333 } 334 335 /** 336 * A table of (int, String) sorted by decreasing int. 337 */ 338 private static class Table { 339 340 private static final Comparator<Integer> DECREASING 341 = Comparator.reverseOrder(); 342 private final TreeMap<Integer, Set<String>> map 343 = new TreeMap<>(DECREASING); 344 345 void put(String label, int n) { 346 if (n == 0) { 347 return; 348 } 349 map.computeIfAbsent(n, k -> new TreeSet<>()).add(label); 350 } 351 352 void print(PrintWriter out) { 353 for (Map.Entry<Integer, Set<String>> e : map.entrySet()) { 354 int count = e.getKey(); 355 Set<String> labels = e.getValue(); 356 for (String label : labels) { 357 out.println(String.format("%6d: %s", count, label)); 358 } 359 } 360 } 361 } 362 } 363 // </editor-fold> 364}