Messages.java revision 3827:44bdefe64114
187866Ssheldonh/* 287866Ssheldonh * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 387866Ssheldonh * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 487866Ssheldonh * 587866Ssheldonh * This code is free software; you can redistribute it and/or modify it 687866Ssheldonh * under the terms of the GNU General Public License version 2 only, as 787866Ssheldonh * published by the Free Software Foundation. Oracle designates this 887866Ssheldonh * particular file as subject to the "Classpath" exception as provided 987866Ssheldonh * by Oracle in the LICENSE file that accompanied this code. 1087866Ssheldonh * 1187866Ssheldonh * This code is distributed in the hope that it will be useful, but WITHOUT 1287866Ssheldonh * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1387866Ssheldonh * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1487866Ssheldonh * version 2 for more details (a copy is included in the LICENSE file that 1587866Ssheldonh * accompanied this code). 1687866Ssheldonh * 1787866Ssheldonh * You should have received a copy of the GNU General Public License version 1887866Ssheldonh * 2 along with this work; if not, write to the Free Software Foundation, 1987866Ssheldonh * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2087866Ssheldonh * 2187866Ssheldonh * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2287866Ssheldonh * or visit www.oracle.com if you need additional information or have any 2387866Ssheldonh * questions. 2487866Ssheldonh */ 2587866Ssheldonh 2687866Ssheldonhpackage com.sun.tools.doclint; 2787866Ssheldonh 2887866Ssheldonhimport java.io.PrintWriter; 2987866Ssheldonhimport java.text.MessageFormat; 3087866Ssheldonhimport java.util.Comparator; 3187866Ssheldonhimport java.util.HashMap; 3288282Ssheldonhimport java.util.Locale; 3387866Ssheldonhimport java.util.Map; 3487866Ssheldonhimport java.util.MissingResourceException; 3587866Ssheldonhimport java.util.ResourceBundle; 3687866Ssheldonhimport java.util.Set; 3787866Ssheldonhimport java.util.TreeMap; 3887866Ssheldonhimport java.util.TreeSet; 3987866Ssheldonh 4087866Ssheldonhimport javax.tools.Diagnostic; 4187866Ssheldonh 4287866Ssheldonhimport com.sun.source.doctree.DocTree; 4387866Ssheldonhimport com.sun.source.tree.Tree; 4487866Ssheldonhimport com.sun.tools.doclint.Env.AccessKind; 4587866Ssheldonhimport com.sun.tools.javac.util.StringUtils; 4687866Ssheldonh 4787866Ssheldonh/** 4887866Ssheldonh * Message reporting for DocLint. 4987866Ssheldonh * 5087866Ssheldonh * Options are used to filter out messages based on group and access level. 5187866Ssheldonh * Support can be enabled for accumulating statistics of different kinds of 5287866Ssheldonh * messages. 5388282Ssheldonh * 5488282Ssheldonh * <p><b>This is NOT part of any supported API. 5588282Ssheldonh * If you write code that depends on this, you do so at your own 5688282Ssheldonh * risk. This code and its internal interfaces are subject to change 5788282Ssheldonh * or deletion without notice.</b></p> 5888282Ssheldonh */ 5988282Ssheldonhpublic class Messages { 6088282Ssheldonh /** 6188282Ssheldonh * Groups used to categorize messages, so that messages in each group 6287866Ssheldonh * can be enabled or disabled via options. 6387866Ssheldonh */ 6487866Ssheldonh public enum Group { 6587866Ssheldonh ACCESSIBILITY, 6687866Ssheldonh HTML, 6787866Ssheldonh MISSING, 6887866Ssheldonh SYNTAX, 6987866Ssheldonh REFERENCE; 7087866Ssheldonh 7187866Ssheldonh String optName() { return StringUtils.toLowerCase(name()); } 7287866Ssheldonh String notOptName() { return "-" + optName(); } 7387866Ssheldonh 7487866Ssheldonh static boolean accepts(String opt) { 7587866Ssheldonh for (Group g: values()) 7687866Ssheldonh if (opt.equals(g.optName())) return true; 7787866Ssheldonh return false; 7887866Ssheldonh } 7987866Ssheldonh } 8087866Ssheldonh 8187866Ssheldonh private final Options options; 8287866Ssheldonh private final Stats stats; 8387866Ssheldonh 8487866Ssheldonh ResourceBundle bundle; 8587866Ssheldonh Env env; 8687866Ssheldonh 8787866Ssheldonh Messages(Env env) { 8887866Ssheldonh this.env = env; 8987866Ssheldonh String name = getClass().getPackage().getName() + ".resources.doclint"; 9087866Ssheldonh bundle = ResourceBundle.getBundle(name, Locale.ENGLISH); 9187866Ssheldonh 9287866Ssheldonh stats = new Stats(bundle); 9387866Ssheldonh options = new Options(stats); 9487866Ssheldonh } 9587866Ssheldonh 9687866Ssheldonh void error(Group group, DocTree tree, String code, Object... args) { 9787866Ssheldonh report(group, Diagnostic.Kind.ERROR, tree, code, args); 9887866Ssheldonh } 9987866Ssheldonh 10087866Ssheldonh void warning(Group group, DocTree tree, String code, Object... args) { 10187866Ssheldonh report(group, Diagnostic.Kind.WARNING, tree, code, args); 10287866Ssheldonh } 10387866Ssheldonh 10487866Ssheldonh void setOptions(String opts) { 10587866Ssheldonh options.setOptions(opts); 10687866Ssheldonh } 10787866Ssheldonh 10887866Ssheldonh void setStatsEnabled(boolean b) { 10987866Ssheldonh stats.setEnabled(b); 11087866Ssheldonh } 11187866Ssheldonh 11287866Ssheldonh void reportStats(PrintWriter out) { 11387866Ssheldonh stats.report(out); 11487866Ssheldonh } 11587866Ssheldonh 11687866Ssheldonh protected void report(Group group, Diagnostic.Kind dkind, DocTree tree, String code, Object... args) { 11787866Ssheldonh if (options.isEnabled(group, env.currAccess)) { 11887866Ssheldonh String msg = (code == null) ? (String) args[0] : localize(code, args); 11987866Ssheldonh env.trees.printMessage(dkind, msg, tree, 12087866Ssheldonh env.currDocComment, env.currPath.getCompilationUnit()); 12187866Ssheldonh 12287866Ssheldonh stats.record(group, dkind, code); 12387866Ssheldonh } 12487866Ssheldonh } 12587866Ssheldonh 12687866Ssheldonh protected void report(Group group, Diagnostic.Kind dkind, Tree tree, String code, Object... args) { 12787866Ssheldonh if (options.isEnabled(group, env.currAccess)) { 12887866Ssheldonh String msg = localize(code, args); 12987866Ssheldonh env.trees.printMessage(dkind, msg, tree, env.currPath.getCompilationUnit()); 13087866Ssheldonh 13187866Ssheldonh stats.record(group, dkind, code); 13287866Ssheldonh } 13387866Ssheldonh } 13487866Ssheldonh 13587866Ssheldonh String localize(String code, Object... args) { 13687866Ssheldonh String msg = bundle.getString(code); 13787866Ssheldonh if (msg == null) { 13887866Ssheldonh StringBuilder sb = new StringBuilder(); 13987866Ssheldonh sb.append("message file broken: code=").append(code); 14087866Ssheldonh if (args.length > 0) { 14187866Ssheldonh sb.append(" arguments={0}"); 14287866Ssheldonh for (int i = 1; i < args.length; i++) { 14387866Ssheldonh sb.append(", {").append(i).append("}"); 14487866Ssheldonh } 14587866Ssheldonh } 14687866Ssheldonh msg = sb.toString(); 14787866Ssheldonh } 14887866Ssheldonh return MessageFormat.format(msg, args); 14987866Ssheldonh } 15087866Ssheldonh 15187866Ssheldonh // <editor-fold defaultstate="collapsed" desc="Options"> 15287866Ssheldonh 15387866Ssheldonh /** 15487866Ssheldonh * Handler for (sub)options specific to message handling. 15587866Ssheldonh */ 15687866Ssheldonh static class Options { 15787866Ssheldonh Map<String, Env.AccessKind> map = new HashMap<>(); 15887866Ssheldonh private final Stats stats; 15987866Ssheldonh 16087866Ssheldonh static boolean isValidOptions(String opts) { 16187866Ssheldonh for (String opt: opts.split(",")) { 16287866Ssheldonh if (!isValidOption(StringUtils.toLowerCase(opt.trim()))) 16387866Ssheldonh return false; 16487866Ssheldonh } 16587866Ssheldonh return true; 16687866Ssheldonh } 16787866Ssheldonh 16887866Ssheldonh private static boolean isValidOption(String opt) { 16987866Ssheldonh if (opt.equals("none") || opt.equals(Stats.OPT)) 17087866Ssheldonh return true; 17187866Ssheldonh 17287866Ssheldonh int begin = opt.startsWith("-") ? 1 : 0; 17387866Ssheldonh int sep = opt.indexOf("/"); 17487866Ssheldonh String grp = opt.substring(begin, (sep != -1) ? sep : opt.length()); 17587866Ssheldonh return ((begin == 0 && grp.equals("all")) || Group.accepts(grp)) 17687866Ssheldonh && ((sep == -1) || AccessKind.accepts(opt.substring(sep + 1))); 17787866Ssheldonh } 17887866Ssheldonh 17987866Ssheldonh Options(Stats stats) { 18087866Ssheldonh this.stats = stats; 18187866Ssheldonh } 18287866Ssheldonh 18387866Ssheldonh /** Determine if a message group is enabled for a particular access level. */ 18487866Ssheldonh boolean isEnabled(Group g, Env.AccessKind access) { 18587866Ssheldonh if (map.isEmpty()) 18688282Ssheldonh map.put("all", Env.AccessKind.PROTECTED); 18788282Ssheldonh 18888282Ssheldonh Env.AccessKind ak = map.get(g.optName()); 18987866Ssheldonh if (ak != null && access.compareTo(ak) >= 0) 19088282Ssheldonh return true; 19188282Ssheldonh 19288282Ssheldonh ak = map.get(ALL); 19387866Ssheldonh if (ak != null && access.compareTo(ak) >= 0) { 19487866Ssheldonh ak = map.get(g.notOptName()); 19587866Ssheldonh if (ak == null || access.compareTo(ak) > 0) // note >, not >= 19687866Ssheldonh return true; 19787866Ssheldonh } 19888282Ssheldonh 19988282Ssheldonh return false; 20088282Ssheldonh } 20187866Ssheldonh 20288282Ssheldonh void setOptions(String opts) { 20388282Ssheldonh if (opts == null) 20488282Ssheldonh setOption(ALL, Env.AccessKind.PRIVATE); 20587866Ssheldonh else { 20687866Ssheldonh for (String opt: opts.split(",")) 20787866Ssheldonh setOption(StringUtils.toLowerCase(opt.trim())); 20887866Ssheldonh } 20987866Ssheldonh } 21087866Ssheldonh 21187866Ssheldonh private void setOption(String arg) throws IllegalArgumentException { 21288282Ssheldonh if (arg.equals(Stats.OPT)) { 21387866Ssheldonh stats.setEnabled(true); 21487866Ssheldonh return; 21587866Ssheldonh } 21688282Ssheldonh 21787866Ssheldonh int sep = arg.indexOf("/"); 21888282Ssheldonh if (sep > 0) { 21988282Ssheldonh Env.AccessKind ak = Env.AccessKind.valueOf(StringUtils.toUpperCase(arg.substring(sep + 1))); 22088282Ssheldonh setOption(arg.substring(0, sep), ak); 22188282Ssheldonh } else { 22288282Ssheldonh setOption(arg, null); 22388282Ssheldonh } 22487866Ssheldonh } 22587866Ssheldonh 22687866Ssheldonh private void setOption(String opt, Env.AccessKind ak) { 22787866Ssheldonh map.put(opt, (ak != null) ? ak 22887866Ssheldonh : opt.startsWith("-") ? Env.AccessKind.PUBLIC : Env.AccessKind.PRIVATE); 22987866Ssheldonh } 23087866Ssheldonh 23187866Ssheldonh private static final String ALL = "all"; 23287866Ssheldonh } 23387866Ssheldonh 23487866Ssheldonh // </editor-fold> 23587866Ssheldonh 23687866Ssheldonh // <editor-fold defaultstate="collapsed" desc="Statistics"> 23787866Ssheldonh 23887866Ssheldonh /** 23988282Ssheldonh * Optionally record statistics of different kinds of message. 24087866Ssheldonh */ 24187866Ssheldonh static class Stats { 24287866Ssheldonh public static final String OPT = "stats"; 24387866Ssheldonh public static final String NO_CODE = ""; 24487866Ssheldonh final ResourceBundle bundle; 24587866Ssheldonh 24687866Ssheldonh // tables only initialized if enabled 24787866Ssheldonh int[] groupCounts; 24887866Ssheldonh int[] dkindCounts; 24987866Ssheldonh Map<String, Integer> codeCounts; 25087866Ssheldonh 25187866Ssheldonh Stats(ResourceBundle bundle) { 25287866Ssheldonh this.bundle = bundle; 25387866Ssheldonh } 25487866Ssheldonh 25587866Ssheldonh void setEnabled(boolean b) { 25687866Ssheldonh if (b) { 25787866Ssheldonh groupCounts = new int[Messages.Group.values().length]; 25887866Ssheldonh dkindCounts = new int[Diagnostic.Kind.values().length]; 25987866Ssheldonh codeCounts = new HashMap<>(); 26087866Ssheldonh } else { 26187866Ssheldonh groupCounts = null; 26287866Ssheldonh dkindCounts = null; 26387866Ssheldonh codeCounts = null; 26487866Ssheldonh } 26587866Ssheldonh } 26687866Ssheldonh 26787866Ssheldonh void record(Messages.Group g, Diagnostic.Kind dkind, String code) { 26887866Ssheldonh if (codeCounts == null) { 26987866Ssheldonh return; 27087866Ssheldonh } 27187866Ssheldonh groupCounts[g.ordinal()]++; 27287866Ssheldonh dkindCounts[dkind.ordinal()]++; 27388282Ssheldonh if (code == null) { 27488282Ssheldonh code = NO_CODE; 27588282Ssheldonh } 27688282Ssheldonh Integer i = codeCounts.get(code); 27788282Ssheldonh codeCounts.put(code, (i == null) ? 1 : i + 1); 27888282Ssheldonh } 27988282Ssheldonh 28088282Ssheldonh void report(PrintWriter out) { 28188282Ssheldonh if (codeCounts == null) { 28288282Ssheldonh return; 28388282Ssheldonh } 28488282Ssheldonh out.println("By group..."); 28588282Ssheldonh Table groupTable = new Table(); 28688282Ssheldonh for (Messages.Group g : Messages.Group.values()) { 28788282Ssheldonh groupTable.put(g.optName(), groupCounts[g.ordinal()]); 28888282Ssheldonh } 28988282Ssheldonh groupTable.print(out); 29088282Ssheldonh out.println(); 29188282Ssheldonh out.println("By diagnostic kind..."); 29288282Ssheldonh Table dkindTable = new Table(); 29388282Ssheldonh for (Diagnostic.Kind k : Diagnostic.Kind.values()) { 29488282Ssheldonh dkindTable.put(StringUtils.toLowerCase(k.toString()), dkindCounts[k.ordinal()]); 29588282Ssheldonh } 29688282Ssheldonh dkindTable.print(out); 29788282Ssheldonh out.println(); 29888282Ssheldonh out.println("By message kind..."); 29988282Ssheldonh Table codeTable = new Table(); 30088282Ssheldonh for (Map.Entry<String, Integer> e : codeCounts.entrySet()) { 30188282Ssheldonh String code = e.getKey(); 30288282Ssheldonh String msg; 30388282Ssheldonh try { 30488282Ssheldonh msg = code.equals(NO_CODE) ? "OTHER" : bundle.getString(code); 30588282Ssheldonh } catch (MissingResourceException ex) { 30688282Ssheldonh msg = code; 30788282Ssheldonh } 30888282Ssheldonh codeTable.put(msg, e.getValue()); 30988282Ssheldonh } 31088282Ssheldonh codeTable.print(out); 31188282Ssheldonh } 31288282Ssheldonh 31388282Ssheldonh /** 31488282Ssheldonh * A table of (int, String) sorted by decreasing int. 31588282Ssheldonh */ 31688282Ssheldonh private static class Table { 31788282Ssheldonh 31888282Ssheldonh private static final Comparator<Integer> DECREASING = (o1, o2) -> o2.compareTo(o1); 31988282Ssheldonh private final TreeMap<Integer, Set<String>> map = new TreeMap<>(DECREASING); 32088282Ssheldonh 32188282Ssheldonh void put(String label, int n) { 32288282Ssheldonh if (n == 0) { 32388282Ssheldonh return; 32488282Ssheldonh } 32588282Ssheldonh Set<String> labels = map.get(n); 32688282Ssheldonh if (labels == null) { 32788282Ssheldonh map.put(n, labels = new TreeSet<>()); 32888282Ssheldonh } 32988282Ssheldonh labels.add(label); 33088282Ssheldonh } 33188282Ssheldonh 33288282Ssheldonh void print(PrintWriter out) { 33388282Ssheldonh for (Map.Entry<Integer, Set<String>> e : map.entrySet()) { 33488282Ssheldonh int count = e.getKey(); 33588282Ssheldonh Set<String> labels = e.getValue(); 33688282Ssheldonh for (String label : labels) { 33788282Ssheldonh out.println(String.format("%6d: %s", count, label)); 33888282Ssheldonh } 33988282Ssheldonh } 34088282Ssheldonh } 34188282Ssheldonh } 34288282Ssheldonh } 34388282Ssheldonh // </editor-fold> 34488282Ssheldonh} 34588282Ssheldonh