1 /*
2  * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.javac.util;
27 
28 import java.util.HashSet;
29 import java.util.Set;
30 import javax.tools.JavaFileObject;
31 
32 import com.sun.tools.javac.code.Lint.LintCategory;
33 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
34 import com.sun.tools.javac.util.JCDiagnostic.Note;
35 import com.sun.tools.javac.util.JCDiagnostic.Warning;
36 
37 
38 /**
39  * A handler to process mandatory warnings, setting up a deferred diagnostic
40  * to be printed at the end of the compilation if some warnings get suppressed
41  * because too many warnings have already been generated.
42  *
43  * Note that the SuppressWarnings annotation can be used to suppress warnings
44  * about conditions that would otherwise merit a warning. Such processing
45  * is done when the condition is detected, and in those cases, no call is
46  * made on any API to generate a warning at all. In consequence, this handler only
47  * Returns to handle those warnings that JLS says must be generated.
48  *
49  *  <p><b>This is NOT part of any supported API.
50  *  If you write code that depends on this, you do so at your own risk.
51  *  This code and its internal interfaces are subject to change or
52  *  deletion without notice.</b>
53  */
54 public class MandatoryWarningHandler {
55 
56     /**
57      * The kinds of different deferred diagnostics that might be generated
58      * if a mandatory warning is suppressed because too many warnings have
59      * already been output.
60      *
61      * The parameter is a fragment used to build an I18N message key for Log.
62      */
63     private enum DeferredDiagnosticKind {
64         /**
65          * This kind is used when a single specific file is found to have warnings
66          * and no similar warnings have already been given.
67          * It generates a message like:
68          *      FILE has ISSUES
69          */
70         IN_FILE(".filename"),
71         /**
72          * This kind is used when a single specific file is found to have warnings
73          * and when similar warnings have already been reported for the file.
74          * It generates a message like:
75          *      FILE has additional ISSUES
76          */
77         ADDITIONAL_IN_FILE(".filename.additional"),
78         /**
79          * This kind is used when multiple files have been found to have warnings,
80          * and none of them have had any similar warnings.
81          * It generates a message like:
82          *      Some files have ISSUES
83          */
84         IN_FILES(".plural"),
85         /**
86          * This kind is used when multiple files have been found to have warnings,
87          * and some of them have had already had specific similar warnings.
88          * It generates a message like:
89          *      Some files have additional ISSUES
90          */
91         ADDITIONAL_IN_FILES(".plural.additional");
92 
DeferredDiagnosticKind(String v)93         DeferredDiagnosticKind(String v) { value = v; }
getKey(String prefix)94         String getKey(String prefix) { return prefix + value; }
95 
96         private final String value;
97     }
98 
99 
100     /**
101      * Create a handler for mandatory warnings.
102      * @param log     The log on which to generate any diagnostics
103      * @param verbose Specify whether or not detailed messages about
104      *                individual instances should be given, or whether an aggregate
105      *                message should be generated at the end of the compilation.
106      *                Typically set via  -Xlint:option.
107      * @param enforceMandatory
108      *                True if mandatory warnings and notes are being enforced.
109      * @param prefix  A common prefix for the set of message keys for
110      *                the messages that may be generated.
111      * @param lc      An associated lint category for the warnings, or null if none.
112      */
MandatoryWarningHandler(Log log, boolean verbose, boolean enforceMandatory, String prefix, LintCategory lc)113     public MandatoryWarningHandler(Log log, boolean verbose,
114                                    boolean enforceMandatory, String prefix,
115                                    LintCategory lc) {
116         this.log = log;
117         this.verbose = verbose;
118         this.prefix = prefix;
119         this.enforceMandatory = enforceMandatory;
120         this.lintCategory = lc;
121     }
122 
123     /**
124      * Report a mandatory warning.
125      */
report(DiagnosticPosition pos, Warning warnKey)126     public void report(DiagnosticPosition pos, Warning warnKey) {
127         JavaFileObject currentSource = log.currentSourceFile();
128 
129         if (verbose) {
130             if (sourcesWithReportedWarnings == null)
131                 sourcesWithReportedWarnings = new HashSet<>();
132 
133             if (log.nwarnings < log.MaxWarnings) {
134                 // generate message and remember the source file
135                 logMandatoryWarning(pos, warnKey);
136                 sourcesWithReportedWarnings.add(currentSource);
137             } else if (deferredDiagnosticKind == null) {
138                 // set up deferred message
139                 if (sourcesWithReportedWarnings.contains(currentSource)) {
140                     // more errors in a file that already has reported warnings
141                     deferredDiagnosticKind = DeferredDiagnosticKind.ADDITIONAL_IN_FILE;
142                 } else {
143                     // warnings in a new source file
144                     deferredDiagnosticKind = DeferredDiagnosticKind.IN_FILE;
145                 }
146                 deferredDiagnosticSource = currentSource;
147                 deferredDiagnosticArg = currentSource;
148             } else if ((deferredDiagnosticKind == DeferredDiagnosticKind.IN_FILE
149                         || deferredDiagnosticKind == DeferredDiagnosticKind.ADDITIONAL_IN_FILE)
150                        && !equal(deferredDiagnosticSource, currentSource)) {
151                 // additional errors in more than one source file
152                 deferredDiagnosticKind = DeferredDiagnosticKind.ADDITIONAL_IN_FILES;
153                 deferredDiagnosticArg = null;
154             }
155         } else {
156             if (deferredDiagnosticKind == null) {
157                 // warnings in a single source
158                 deferredDiagnosticKind = DeferredDiagnosticKind.IN_FILE;
159                 deferredDiagnosticSource = currentSource;
160                 deferredDiagnosticArg = currentSource;
161             }  else if (deferredDiagnosticKind == DeferredDiagnosticKind.IN_FILE &&
162                         !equal(deferredDiagnosticSource, currentSource)) {
163                 // warnings in multiple source files
164                 deferredDiagnosticKind = DeferredDiagnosticKind.IN_FILES;
165                 deferredDiagnosticArg = null;
166             }
167         }
168     }
169 
170     /**
171      * Report any diagnostic that might have been deferred by previous calls of report().
172      */
reportDeferredDiagnostic()173     public void reportDeferredDiagnostic() {
174         if (deferredDiagnosticKind != null) {
175             if (deferredDiagnosticArg == null)
176                 logMandatoryNote(deferredDiagnosticSource, deferredDiagnosticKind.getKey(prefix));
177             else
178                 logMandatoryNote(deferredDiagnosticSource, deferredDiagnosticKind.getKey(prefix), deferredDiagnosticArg);
179 
180             if (!verbose)
181                 logMandatoryNote(deferredDiagnosticSource, prefix + ".recompile");
182         }
183     }
184 
185     /**
186      * Check two objects, each possibly null, are either both null or are equal.
187      */
equal(Object o1, Object o2)188     private static boolean equal(Object o1, Object o2) {
189         return ((o1 == null || o2 == null) ? (o1 == o2) : o1.equals(o2));
190     }
191 
192     /**
193      * The log to which to report warnings.
194      */
195     private Log log;
196 
197     /**
198      * Whether or not to report individual warnings, or simply to report a
199      * single aggregate warning at the end of the compilation.
200      */
201     private boolean verbose;
202 
203     /**
204      * The common prefix for all I18N message keys generated by this handler.
205      */
206     private String prefix;
207 
208     /**
209      * A set containing the names of the source files for which specific
210      * warnings have been generated -- i.e. in verbose mode.  If a source name
211      * appears in this list, then deferred diagnostics will be phrased to
212      * include "additionally"...
213      */
214     private Set<JavaFileObject> sourcesWithReportedWarnings;
215 
216     /**
217      * A variable indicating the latest best guess at what the final
218      * deferred diagnostic will be. Initially as specific and helpful
219      * as possible, as more warnings are reported, the scope of the
220      * diagnostic will be broadened.
221      */
222     private DeferredDiagnosticKind deferredDiagnosticKind;
223 
224     /**
225      * If deferredDiagnosticKind is IN_FILE or ADDITIONAL_IN_FILE, this variable
226      * gives the value of log.currentSource() for the file in question.
227      */
228     private JavaFileObject deferredDiagnosticSource;
229 
230     /**
231      * An optional argument to be used when constructing the
232      * deferred diagnostic message, based on deferredDiagnosticKind.
233      * This variable should normally be set/updated whenever
234      * deferredDiagnosticKind is updated.
235      */
236     private Object deferredDiagnosticArg;
237 
238     /**
239      * True if mandatory warnings and notes are being enforced.
240      */
241     private final boolean enforceMandatory;
242 
243     /**
244      * A LintCategory to be included in point-of-use diagnostics to indicate
245      * how messages might be suppressed (i.e. with @SuppressWarnings).
246      */
247     private final LintCategory lintCategory;
248 
249     /**
250      * Reports a mandatory warning to the log.  If mandatory warnings
251      * are not being enforced, treat this as an ordinary warning.
252      */
logMandatoryWarning(DiagnosticPosition pos, Warning warnKey)253     private void logMandatoryWarning(DiagnosticPosition pos, Warning warnKey) {
254         // Note: the following log methods are safe if lintCategory is null.
255         if (enforceMandatory)
256             log.mandatoryWarning(lintCategory, pos, warnKey);
257         else
258             log.warning(lintCategory, pos, warnKey);
259     }
260 
261     /**
262      * Reports a mandatory note to the log.  If mandatory notes are
263      * not being enforced, treat this as an ordinary note.
264      */
logMandatoryNote(JavaFileObject file, String msg, Object... args)265     private void logMandatoryNote(JavaFileObject file, String msg, Object... args) {
266         if (enforceMandatory)
267             log.mandatoryNote(file, new Note("compiler", msg, args));
268         else
269             log.note(file, new Note("compiler", msg, args));
270     }
271 }
272