001    /*
002     * Copyright 2005-2008 Sun Microsystems, Inc.  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.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.javac.util;
027    
028    import java.util.HashSet;
029    import java.util.Set;
030    import javax.tools.JavaFileObject;
031    
032    import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
033    
034    
035    /**
036     * A handler to process mandatory warnings, setting up a deferred diagnostic
037     * to be printed at the end of the compilation if some warnings get suppressed
038     * because too many warnings have already been generated.
039     *
040     * Note that the SuppressWarnings annotation can be used to suppress warnings
041     * about conditions that would otherwise merit a warning. Such processing
042     * is done when the condition is detected, and in those cases, no call is
043     * made on any API to generate a warning at all. In consequence, this handler only
044     * gets to handle those warnings that JLS says must be generated.
045     *
046     *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
047     *  you write code that depends on this, you do so at your own risk.
048     *  This code and its internal interfaces are subject to change or
049     *  deletion without notice.</b>
050     */
051    public class MandatoryWarningHandler {
052    
053        /**
054         * The kinds of different deferred diagnostics that might be generated
055         * if a mandatory warning is suppressed because too many warnings have
056         * already been output.
057         *
058         * The parameter is a fragment used to build an I18N message key for Log.
059         */
060        private enum DeferredDiagnosticKind {
061            /**
062             * This kind is used when a single specific file is found to have warnings
063             * and no similar warnings have already been given.
064             * It generates a message like:
065             *      FILE has ISSUES
066             */
067            IN_FILE(".filename"),
068            /**
069             * This kind is used when a single specific file is found to have warnings
070             * and when similar warnings have already been reported for the file.
071             * It generates a message like:
072             *      FILE has additional ISSUES
073             */
074            ADDITIONAL_IN_FILE(".filename.additional"),
075            /**
076             * This kind is used when multiple files have been found to have warnings,
077             * and none of them have had any similar warnings.
078             * It generates a message like:
079             *      Some files have ISSUES
080             */
081            IN_FILES(".plural"),
082            /**
083             * This kind is used when multiple files have been found to have warnings,
084             * and some of them have had already had specific similar warnings.
085             * It generates a message like:
086             *      Some files have additional ISSUES
087             */
088            ADDITIONAL_IN_FILES(".plural.additional");
089    
090            DeferredDiagnosticKind(String v) { value = v; }
091            String getKey(String prefix) { return prefix + value; }
092    
093            private String value;
094        }
095    
096    
097        /**
098         * Create a handler for mandatory warnings.
099         * @param log     The log on which to generate any diagnostics
100         * @param verbose Specify whether or not detailed messages about
101         *                individual instances should be given, or whether an aggregate
102         *                message should be generated at the end of the compilation.
103         *                Typically set via  -Xlint:option.
104         * @param enforceMandatory
105         *                True if mandatory warnings and notes are being enforced.
106         * @param prefix  A common prefix for the set of message keys for
107         *                the messages that may be generated.
108         */
109        public MandatoryWarningHandler(Log log, boolean verbose,
110                                       boolean enforceMandatory, String prefix) {
111            this.log = log;
112            this.verbose = verbose;
113            this.prefix = prefix;
114            this.enforceMandatory = enforceMandatory;
115        }
116    
117        /**
118         * Report a mandatory warning.
119         */
120        public void report(DiagnosticPosition pos, String msg, Object... args) {
121            JavaFileObject currentSource = log.currentSourceFile();
122    
123            if (verbose) {
124                if (sourcesWithReportedWarnings == null)
125                    sourcesWithReportedWarnings = new HashSet<JavaFileObject>();
126    
127                if (log.nwarnings < log.MaxWarnings) {
128                    // generate message and remember the source file
129                    logMandatoryWarning(pos, msg, args);
130                    sourcesWithReportedWarnings.add(currentSource);
131                } else if (deferredDiagnosticKind == null) {
132                    // set up deferred message
133                    if (sourcesWithReportedWarnings.contains(currentSource)) {
134                        // more errors in a file that already has reported warnings
135                        deferredDiagnosticKind = DeferredDiagnosticKind.ADDITIONAL_IN_FILE;
136                    } else {
137                        // warnings in a new source file
138                        deferredDiagnosticKind = DeferredDiagnosticKind.IN_FILE;
139                    }
140                    deferredDiagnosticSource = currentSource;
141                    deferredDiagnosticArg = currentSource;
142                } else if ((deferredDiagnosticKind == DeferredDiagnosticKind.IN_FILE
143                            || deferredDiagnosticKind == DeferredDiagnosticKind.ADDITIONAL_IN_FILE)
144                           && !equal(deferredDiagnosticSource, currentSource)) {
145                    // additional errors in more than one source file
146                    deferredDiagnosticKind = DeferredDiagnosticKind.ADDITIONAL_IN_FILES;
147                    deferredDiagnosticArg = null;
148                }
149            } else {
150                if (deferredDiagnosticKind == null) {
151                    // warnings in a single source
152                    deferredDiagnosticKind = DeferredDiagnosticKind.IN_FILE;
153                    deferredDiagnosticSource = currentSource;
154                    deferredDiagnosticArg = currentSource;
155                }  else if (deferredDiagnosticKind == DeferredDiagnosticKind.IN_FILE &&
156                            !equal(deferredDiagnosticSource, currentSource)) {
157                    // warnings in multiple source files
158                    deferredDiagnosticKind = DeferredDiagnosticKind.IN_FILES;
159                    deferredDiagnosticArg = null;
160                }
161            }
162        }
163    
164        /**
165         * Report any diagnostic that might have been deferred by previous calls of report().
166         */
167        public void reportDeferredDiagnostic() {
168            if (deferredDiagnosticKind != null) {
169                if (deferredDiagnosticArg == null)
170                    logMandatoryNote(deferredDiagnosticSource, deferredDiagnosticKind.getKey(prefix));
171                else
172                    logMandatoryNote(deferredDiagnosticSource, deferredDiagnosticKind.getKey(prefix), deferredDiagnosticArg);
173    
174                if (!verbose)
175                    logMandatoryNote(deferredDiagnosticSource, prefix + ".recompile");
176            }
177        }
178    
179        /**
180         * Check two objects, each possibly null, are either both null or are equal.
181         */
182        private static boolean equal(Object o1, Object o2) {
183            return ((o1 == null || o2 == null) ? (o1 == o2) : o1.equals(o2));
184        }
185    
186        /**
187         * The log to which to report warnings.
188         */
189        private Log log;
190    
191        /**
192         * Whether or not to report individual warnings, or simply to report a
193         * single aggregate warning at the end of the compilation.
194         */
195        private boolean verbose;
196    
197        /**
198         * The common prefix for all I18N message keys generated by this handler.
199         */
200        private String prefix;
201    
202        /**
203         * A set containing the names of the source files for which specific
204         * warnings have been generated -- i.e. in verbose mode.  If a source name
205         * appears in this list, then deferred diagnostics will be phrased to
206         * include "additionally"...
207         */
208        private Set<JavaFileObject> sourcesWithReportedWarnings;
209    
210        /**
211         * A variable indicating the latest best guess at what the final
212         * deferred diagnostic will be. Initially as specific and helpful
213         * as possible, as more warnings are reported, the scope of the
214         * diagnostic will be broadened.
215         */
216        private DeferredDiagnosticKind deferredDiagnosticKind;
217    
218        /**
219         * If deferredDiagnosticKind is IN_FILE or ADDITIONAL_IN_FILE, this variable
220         * gives the value of log.currentSource() for the file in question.
221         */
222        private JavaFileObject deferredDiagnosticSource;
223    
224        /**
225         * An optional argument to be used when constructing the
226         * deferred diagnostic message, based on deferredDiagnosticKind.
227         * This variable should normally be set/updated whenever
228         * deferredDiagnosticKind is updated.
229         */
230        private Object deferredDiagnosticArg;
231    
232        /**
233         * True if mandatory warnings and notes are being enforced.
234         */
235        private final boolean enforceMandatory;
236    
237        /**
238         * Reports a mandatory warning to the log.  If mandatory warnings
239         * are not being enforced, treat this as an ordinary warning.
240         */
241        private void logMandatoryWarning(DiagnosticPosition pos, String msg,
242                                         Object... args) {
243            if (enforceMandatory)
244                log.mandatoryWarning(pos, msg, args);
245            else
246                log.warning(pos, msg, args);
247        }
248    
249        /**
250         * Reports a mandatory note to the log.  If mandatory notes are
251         * not being enforced, treat this as an ordinary note.
252         */
253        private void logMandatoryNote(JavaFileObject file, String msg, Object... args) {
254            if (enforceMandatory)
255                log.mandatoryNote(file, msg, args);
256            else
257                log.note(file, msg, args);
258        }
259    }