1 /*
2  * Copyright (c) 1997, 2019, 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 jdk.javadoc.internal.tool;
27 
28 
29 import java.io.PrintWriter;
30 import java.util.Locale;
31 import java.util.ResourceBundle;
32 
33 import javax.lang.model.element.Element;
34 import javax.tools.Diagnostic.Kind;
35 
36 import com.sun.tools.javac.util.Context.Factory;
37 import jdk.javadoc.doclet.Reporter;
38 import com.sun.source.tree.CompilationUnitTree;
39 import com.sun.source.util.DocSourcePositions;
40 import com.sun.source.util.DocTreePath;
41 import com.sun.source.util.TreePath;
42 import com.sun.tools.javac.api.JavacTrees;
43 import com.sun.tools.javac.tree.JCTree;
44 import com.sun.tools.javac.util.Context;
45 import com.sun.tools.javac.util.JCDiagnostic;
46 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
47 import com.sun.tools.javac.util.JavacMessages;
48 import com.sun.tools.javac.util.Log;
49 
50 /**
51  * Utility for integrating with javadoc tools and for localization.
52  * Handle resources, access to error and warning counts and
53  * message formatting.
54  *
55  *  <p><b>This is NOT part of any supported API.
56  *  If you write code that depends on this, you do so at your own risk.
57  *  This code and its internal interfaces are subject to change or
58  *  deletion without notice.</b>
59  *
60  * @see java.util.ResourceBundle
61  * @see java.text.MessageFormat
62  */
63 public class Messager extends Log implements Reporter {
64     final Context context;
65 
66     /** Get the current messager, which is also the compiler log. */
instance0(Context context)67     public static Messager instance0(Context context) {
68         Log instance = context.get(logKey);
69         if (instance == null || !(instance instanceof Messager))
70             throw new InternalError("no messager instance!");
71         return (Messager)instance;
72     }
73 
preRegister(Context context, final String programName)74     public static void preRegister(Context context,
75                                    final String programName) {
76         context.put(logKey, (Factory<Log>)c -> new Messager(c, programName));
77     }
78 
preRegister(Context context, final String programName, final PrintWriter outWriter, final PrintWriter errWriter)79     public static void preRegister(Context context, final String programName,
80             final PrintWriter outWriter, final PrintWriter errWriter) {
81         context.put(logKey, (Factory<Log>)c -> new Messager(c, programName, outWriter, errWriter));
82     }
83 
84     @Override
print(Kind kind, String msg)85     public void print(Kind kind, String msg) {
86         switch (kind) {
87             case ERROR:
88                 printError(msg);
89                 return;
90             case WARNING:
91             case MANDATORY_WARNING:
92                 printWarning(msg);
93                 return;
94             default:
95                 printNotice(msg);
96                 return;
97         }
98     }
99 
100     @Override
print(Kind kind, DocTreePath path, String msg)101     public void print(Kind kind, DocTreePath path, String msg) {
102         switch (kind) {
103             case ERROR:
104                 printError(path, msg);
105                 return;
106             case WARNING:
107             case MANDATORY_WARNING:
108                 printWarning(path, msg);
109                 return;
110             default:
111                 printWarning(path, msg);
112                 return;
113         }
114     }
115 
116     @Override
print(Kind kind, Element e, String msg)117     public void print(Kind kind, Element e, String msg) {
118                 switch (kind) {
119             case ERROR:
120                 printError(e, msg);
121                 return;
122             case WARNING:
123             case MANDATORY_WARNING:
124                 printWarning(e, msg);
125                 return;
126             case NOTE:
127                 printNotice(e, msg);
128                 return;
129             default:
130                 throw new IllegalArgumentException(String.format("unexpected option %s", kind));
131         }
132     }
133 
134     final String programName;
135 
136     private Locale locale;
137     private final JavacMessages messages;
138     private final JCDiagnostic.Factory javadocDiags;
139 
140     /** The default writer for diagnostics
141      */
142     static final PrintWriter defaultOutWriter = new PrintWriter(System.out);
143     static final PrintWriter defaultErrWriter = new PrintWriter(System.err);
144 
145     /**
146      * Constructor
147      * @param programName  Name of the program (for error messages).
148      */
Messager(Context context, String programName)149     public Messager(Context context, String programName) {
150         this(context, programName, defaultOutWriter, defaultErrWriter);
151     }
152 
153     /**
154      * Constructor
155      * @param programName  Name of the program (for error messages).
156      * @param outWriter    Stream for notices etc.
157      * @param errWriter    Stream for errors and warnings
158      */
159     @SuppressWarnings("deprecation")
Messager(Context context, String programName, PrintWriter outWriter, PrintWriter errWriter)160     public Messager(Context context, String programName, PrintWriter outWriter, PrintWriter errWriter) {
161         super(context, errWriter, errWriter, outWriter);
162         messages = JavacMessages.instance(context);
163         messages.add(locale -> ResourceBundle.getBundle("jdk.javadoc.internal.tool.resources.javadoc",
164                                                          locale));
165         javadocDiags = new JCDiagnostic.Factory(messages, "javadoc");
166         this.programName = programName;
167         this.context = context;
168         locale = Locale.getDefault();
169     }
170 
setLocale(Locale locale)171     public void setLocale(Locale locale) {
172         this.locale = locale;
173     }
174 
175     /**
176      * get and format message string from resource
177      *
178      * @param key selects message from resource
179      * @param args arguments for the message
180      */
getText(String key, Object... args)181     String getText(String key, Object... args) {
182         return messages.getLocalizedString(locale, key, args);
183     }
184 
getDiagSource(DocTreePath path)185     private String getDiagSource(DocTreePath path) {
186         if (path == null || path.getTreePath() == null) {
187             return programName;
188         }
189         JavacTrees trees = JavacTrees.instance(context);
190         DocSourcePositions sourcePositions = trees.getSourcePositions();
191         CompilationUnitTree cu = path.getTreePath().getCompilationUnit();
192         long spos = sourcePositions.getStartPosition(cu, path.getDocComment(), path.getLeaf());
193         long lineNumber = cu.getLineMap().getLineNumber(spos);
194         String fname = cu.getSourceFile().getName();
195         String posString = fname + ":" + lineNumber;
196         return posString;
197     }
198 
getDiagSource(Element e)199     private String getDiagSource(Element e) {
200         if (e == null) {
201             return programName;
202         }
203         JavacTrees trees = JavacTrees.instance(context);
204         TreePath path = trees.getPath(e);
205         if (path == null) {
206             return programName;
207         }
208         DocSourcePositions sourcePositions = trees.getSourcePositions();
209         JCTree tree = trees.getTree(e);
210         CompilationUnitTree cu = path.getCompilationUnit();
211         long spos = sourcePositions.getStartPosition(cu, tree);
212         long lineNumber = cu.getLineMap().getLineNumber(spos);
213         String fname = cu.getSourceFile().getName();
214         String posString = fname + ":" + lineNumber;
215         return posString;
216     }
217 
218     /**
219      * Print error message, increment error count.
220      * Part of DocErrorReporter.
221      *
222      * @param msg message to print
223      */
printError(String msg)224     public void printError(String msg) {
225         printError((DocTreePath)null, msg);
226     }
227 
printError(DocTreePath path, String msg)228     public void printError(DocTreePath path, String msg) {
229         String prefix = getDiagSource(path);
230         if (diagListener != null) {
231             report(DiagnosticType.ERROR, prefix, msg);
232             return;
233         }
234         printError(prefix, msg);
235     }
236 
printError(Element e, String msg)237     public void printError(Element e, String msg) {
238         String prefix = getDiagSource(e);
239         if (diagListener != null) {
240             report(DiagnosticType.ERROR, prefix, msg);
241             return;
242         }
243         printError(prefix, msg);
244     }
245 
printErrorUsingKey(String key, Object... args)246     public void printErrorUsingKey(String key, Object... args) {
247         printError((Element)null, getText(key, args));
248     }
249 
250     // print the error and increment count
printError(String prefix, String msg)251     private void printError(String prefix, String msg) {
252         if (nerrors < MaxErrors) {
253             PrintWriter errWriter = getWriter(WriterKind.ERROR);
254             printRawLines(errWriter, prefix + ": " + getText("javadoc.error") + " - " + msg);
255             errWriter.flush();
256             prompt();
257             nerrors++;
258         }
259     }
260 
261     /**
262      * Print warning message, increment warning count.
263      * Part of DocErrorReporter.
264      *
265      * @param msg message to print
266      */
printWarning(String msg)267     public void printWarning(String msg) {
268         printWarning((DocTreePath)null, msg);
269     }
270 
printWarningUsingKey(String key, Object... args)271     public void printWarningUsingKey(String key, Object... args) {
272         printWarning((Element)null, getText(key, args));
273     }
274 
printWarning(Element e, String key, Object... args)275     public void printWarning(Element e, String key, Object... args) {
276         printWarning(getText(key, args));
277     }
278 
printWarning(DocTreePath path, String msg)279     public void printWarning(DocTreePath path, String msg) {
280         String prefix = getDiagSource(path);
281         if (diagListener != null) {
282             report(DiagnosticType.WARNING, prefix, msg);
283             return;
284         }
285         printWarning(prefix, msg);
286     }
287 
printWarning(Element e, String msg)288     public void printWarning(Element e, String msg) {
289         String prefix = getDiagSource(e);
290         if (diagListener != null) {
291             report(DiagnosticType.WARNING, prefix, msg);
292             return;
293         }
294         printWarning(prefix, msg);
295     }
296 
297     // print the warning and increment count
printWarning(String prefix, String msg)298     private void printWarning(String prefix, String msg) {
299         if (nwarnings < MaxWarnings) {
300             PrintWriter warnWriter = getWriter(WriterKind.WARNING);
301             printRawLines(warnWriter, prefix + ": " + getText("javadoc.warning") + " - " + msg);
302             warnWriter.flush();
303             nwarnings++;
304         }
305     }
306 
307     /**
308      * Print a message.
309      * Part of DocErrorReporter.
310      *
311      * @param msg message to print
312      */
printNotice(String msg)313     public void printNotice(String msg) {
314         printNotice((DocTreePath)null, msg);
315     }
316 
printNotice(DocTreePath path, String msg)317     public void printNotice(DocTreePath path, String msg) {
318         String prefix = getDiagSource(path);
319         if (diagListener != null) {
320             report(DiagnosticType.NOTE, null, prefix + ": " + msg);
321             return;
322         }
323 
324         PrintWriter noticeWriter = getWriter(WriterKind.NOTICE);
325         if (path == null) {
326             printRawLines(noticeWriter, msg);
327         } else {
328             printRawLines(noticeWriter, prefix + ": " + msg);
329         }
330         noticeWriter.flush();
331     }
332 
printNotice(Element e, String msg)333     public void printNotice(Element e, String msg) {
334         String pos = getDiagSource(e);
335         if (diagListener != null) {
336             report(DiagnosticType.NOTE, pos, msg);
337             return;
338         }
339 
340         PrintWriter noticeWriter = getWriter(WriterKind.NOTICE);
341         if (e == null) {
342             printRawLines(noticeWriter, msg);
343         } else {
344             printRawLines(noticeWriter, pos + ": " + msg);
345         }
346         noticeWriter.flush();
347     }
348 
349     /**
350      * Print a message.
351      *
352      * @param key selects message from resource
353      */
notice(String key, Object... args)354     public void notice(String key, Object... args) {
355         printNotice(getText(key, args));
356     }
357 
358     /**
359      * Returns true if errors have been recorded.
360      */
hasErrors()361     public boolean hasErrors() {
362         return nerrors != 0;
363     }
364 
365     /**
366      * Returns true if warnings have been recorded.
367      */
hasWarnings()368     public boolean hasWarnings() {
369         return nwarnings != 0;
370     }
371 
372     /**
373      * Print exit message.
374      */
printErrorWarningCounts()375     public void printErrorWarningCounts() {
376         if (nerrors > 0) {
377             notice((nerrors > 1) ? "main.errors" : "main.error",
378                    "" + nerrors);
379         }
380         if (nwarnings > 0) {
381             notice((nwarnings > 1) ?  "main.warnings" : "main.warning",
382                    "" + nwarnings);
383         }
384     }
385 
report(DiagnosticType type, String pos, String msg)386     private void report(DiagnosticType type, String pos, String msg) {
387         switch (type) {
388             case ERROR:
389             case WARNING:
390                 Object prefix = (pos == null) ? programName : pos;
391                 report(javadocDiags.create(type, null, null, "msg", prefix, msg));
392                 break;
393 
394             case NOTE:
395                 String key = (pos == null) ? "msg" : "pos.msg";
396                 report(javadocDiags.create(type, null, null, key, pos, msg));
397                 break;
398 
399             default:
400                 throw new IllegalArgumentException(type.toString());
401         }
402     }
403 }
404