1 /*
2  * Copyright (c) 2009, 2016, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /**
25  * @test
26  * @bug     6769027 8006694
27  * @summary Source line should be displayed immediately after the first diagnostic line
28  * @modules jdk.compiler/com.sun.tools.javac.api
29  *          jdk.compiler/com.sun.tools.javac.util
30  * @run main/othervm T6769027
31  */
32 
33 // use /othervm to avoid locale issues
34 
35 import java.net.URI;
36 import java.util.ResourceBundle;
37 import java.util.regex.Matcher;
38 import javax.tools.*;
39 import com.sun.tools.javac.util.*;
40 
41 public class T6769027 {
42 
43     enum OutputKind {
44         RAW("rawDiagnostics","rawDiagnostics"),
45         BASIC("","");
46 
47         String key;
48         String value;
49 
init(Options opts)50         void init(Options opts) {
51             opts.put(key, value);
52         }
53 
OutputKind(String key, String value)54         OutputKind(String key, String value) {
55             this.key = key;
56             this.value = value;
57         }
58     }
59 
60     enum CaretKind {
61         DEFAULT("", ""),
62         SHOW("diags.showCaret","true"),
63         HIDE("diags.showCaret","false");
64 
65         String key;
66         String value;
67 
init(Options opts)68         void init(Options opts) {
69             opts.put(key, value);
70         }
71 
CaretKind(String key, String value)72         CaretKind(String key, String value) {
73             this.key = key;
74             this.value = value;
75         }
76 
isEnabled()77         boolean isEnabled() {
78             return this == DEFAULT || this == SHOW;
79         }
80     }
81 
82     enum SourceLineKind {
83         DEFAULT("", ""),
84         AFTER_SUMMARY("diags.sourcePosition", "top"),
85         BOTTOM("diags.sourcePosition", "bottom");
86 
87         String key;
88         String value;
89 
init(Options opts)90         void init(Options opts) {
91             opts.put(key, value);
92         }
93 
SourceLineKind(String key, String value)94         SourceLineKind(String key, String value) {
95             this.key = key;
96             this.value = value;
97         }
98 
isAfterSummary()99         boolean isAfterSummary() {
100             return this == DEFAULT || this == AFTER_SUMMARY;
101         }
102     }
103 
104     enum XDiagsSource {
105         DEFAULT(""),
106         SOURCE("source"),
107         NO_SOURCE("-source");
108 
109         String flag;
110 
init(Options opts)111         void init(Options opts) {
112             if (this != DEFAULT) {
113                 String flags = opts.get("diags.formatterOptions");
114                 flags = flags == null ? flag : flags + "," + flag;
115                 opts.put("diags.formatterOptions", flags);
116             }
117         }
118 
XDiagsSource(String flag)119         XDiagsSource(String flag) {
120             this.flag = flag;
121         }
122 
getOutput(CaretKind caretKind, IndentKind indent, OutputKind outKind)123         String getOutput(CaretKind caretKind, IndentKind indent, OutputKind outKind) {
124             String spaces = (outKind == OutputKind.BASIC) ? indent.string : "";
125             return "\n" + spaces + "This is a source line" +
126                    (caretKind.isEnabled() ? "\n" + spaces + "     ^" : "");
127         }
128     }
129 
130     enum XDiagsCompact {
131         DEFAULT(""),
132         COMPACT("short"),
133         NO_COMPACT("-short");
134 
135         String flag;
136 
init(Options opts)137         void init(Options opts) {
138             if (this != DEFAULT) {
139                 String flags = opts.get("diags.formatterOptions");
140                 flags = flags == null ? flag : flags + "," + flag;
141                 opts.put("diags.formatterOptions", flags);
142             }
143         }
144 
XDiagsCompact(String flag)145         XDiagsCompact(String flag) {
146             this.flag = flag;
147         }
148     }
149 
150     enum ErrorKind {
151         SINGLE("single",
152             "compiler.err.single: Hello!",
153             "KXThis is a test error message Hello!"),
154         DOUBLE("double",
155             "compiler.err.double: Hello!",
156             "KXThis is a test error message.\n" +
157             "KXYThis is another line of the above error message Hello!");
158 
159         String key;
160         String rawOutput;
161         String nonRawOutput;
162 
key()163         String key() {
164             return key;
165         }
166 
ErrorKind(String key, String rawOutput, String nonRawOutput)167         ErrorKind(String key, String rawOutput, String nonRawOutput) {
168             this.key = key;
169             this.rawOutput = rawOutput;
170             this.nonRawOutput = nonRawOutput;
171         }
172 
getOutput(OutputKind outKind, IndentKind summaryIndent, IndentKind detailsIndent)173         String getOutput(OutputKind outKind, IndentKind summaryIndent, IndentKind detailsIndent) {
174             return outKind == OutputKind.RAW ?
175                 rawOutput :
176                 nonRawOutput.replace("X", summaryIndent.string).replace("Y", detailsIndent.string).replace("K", "");
177         }
178 
getOutput(OutputKind outKind, IndentKind summaryIndent, IndentKind detailsIndent, String indent)179         String getOutput(OutputKind outKind, IndentKind summaryIndent, IndentKind detailsIndent, String indent) {
180             return outKind == OutputKind.RAW ?
181                 rawOutput :
182                 nonRawOutput.replace("X", summaryIndent.string).replace("Y", detailsIndent.string).replace("K", indent);
183         }
184     }
185 
186     enum MultilineKind {
187         NONE(0),
188         DOUBLE(1),
189         NESTED(2),
190         DOUBLE_NESTED(3);
191 
192         static String[][] rawTemplates = {
193             {"", ",{(E),(E)}", ",{(E,{(E)})}", ",{(E,{(E)}),(E,{(E)})}"}, //ENABLED
194             {"", "", "", "",""}, //DISABLED
195             {"", ",{(E)}", ",{(E,{(E)})}", ",{(E,{(E)})}"}, //LIMIT_LENGTH
196             {"", ",{(E),(E)}", ",{(E)}", ",{(E),(E)}"}, //LIMIT_DEPTH
197             {"", ",{(E)}", ",{(E)}", ",{(E)}"}}; //LIMIT_BOTH
198 
199         static String[][] basicTemplates = {
200             {"", "\nE\nE", "\nE\nQ", "\nE\nQ\nE\nQ"}, //ENABLED
201             {"", "", "", "",""}, //DISABLED
202             {"", "\nE", "\nE\nQ", "\nE\nQ"}, //LIMIT_LENGTH
203             {"", "\nE\nE", "\nE", "\nE\nE"}, //LIMIT_DEPTH
204             {"", "\nE", "\nE", "\nE"}}; //LIMIT_BOTH
205 
206 
207         int index;
208 
MultilineKind(int index)209         MultilineKind (int index) {
210             this.index = index;
211         }
212 
isDouble()213         boolean isDouble() {
214             return this == DOUBLE || this == DOUBLE_NESTED;
215         }
216 
isNested()217         boolean isNested() {
218             return this == NESTED || this == DOUBLE_NESTED;
219         }
220 
getOutput(OutputKind outKind, ErrorKind errKind, MultilinePolicy policy, IndentKind summaryIndent, IndentKind detailsIndent, IndentKind multiIndent)221         String getOutput(OutputKind outKind, ErrorKind errKind, MultilinePolicy policy,
222                 IndentKind summaryIndent, IndentKind detailsIndent, IndentKind multiIndent) {
223             String constIndent = (errKind == ErrorKind.DOUBLE) ?
224                 summaryIndent.string + detailsIndent.string :
225                 summaryIndent.string;
226             constIndent += multiIndent.string;
227 
228             String errMsg1 = errKind.getOutput(outKind, summaryIndent, detailsIndent, constIndent);
229             String errMsg2 = errKind.getOutput(outKind, summaryIndent, detailsIndent, constIndent + constIndent);
230 
231             errMsg1 = errMsg1.replaceAll("compiler.err", "compiler.misc");
232             errMsg1 = errMsg1.replaceAll("error message", "subdiagnostic");
233             errMsg2 = errMsg2.replaceAll("compiler.err", "compiler.misc");
234             errMsg2 = errMsg2.replaceAll("error message", "subdiagnostic");
235 
236             String template = outKind == OutputKind.RAW ?
237                 rawTemplates[policy.index][index] :
238                 basicTemplates[policy.index][index];
239 
240             template = template.replaceAll("E", errMsg1);
241             return template.replaceAll("Q", errMsg2);
242         }
243     }
244 
245     enum MultilinePolicy {
246         ENABLED(0, "diags.multilinePolicy", "enabled"),
247         DISABLED(1, "diags.multilinePolicy", "disabled"),
248         LIMIT_LENGTH(2, "diags.multilinePolicy", "limit:1:*"),
249         LIMIT_DEPTH(3, "diags.multilinePolicy", "limit:*:1"),
250         LIMIT_BOTH(4, "diags.multilinePolicy", "limit:1:1");
251 
252         String name;
253         String value;
254         int index;
255 
MultilinePolicy(int index, String name, String value)256         MultilinePolicy(int index, String name, String value) {
257             this.name = name;
258             this.value = value;
259             this.index = index;
260         }
261 
init(Options options)262         void init(Options options) {
263             options.put(name, value);
264         }
265     }
266 
267     enum PositionKind {
268         NOPOS(Position.NOPOS, "- ", "error: "),
269         POS(5, "Test.java:1:6: ", "/Test.java:1: error: ");
270 
271         int pos;
272         String rawOutput;
273         String nonRawOutput;
274 
PositionKind(int pos, String rawOutput, String nonRawOutput)275         PositionKind(int pos, String rawOutput, String nonRawOutput) {
276             this.pos = pos;
277             this.rawOutput = rawOutput;
278             this.nonRawOutput = nonRawOutput;
279         }
280 
pos()281         JCDiagnostic.DiagnosticPosition pos() {
282             return new JCDiagnostic.SimpleDiagnosticPosition(pos);
283         }
284 
getOutput(OutputKind outputKind)285         String getOutput(OutputKind outputKind) {
286             return outputKind == OutputKind.RAW ?
287                 rawOutput :
288                 nonRawOutput;
289         }
290     }
291 
292     static class MyFileObject extends SimpleJavaFileObject {
293         private String text;
MyFileObject(String text)294         public MyFileObject(String text) {
295             super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
296             this.text = text;
297         }
298         @Override
getCharContent(boolean ignoreEncodingErrors)299         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
300             return text;
301         }
302     }
303 
304     enum IndentKind {
305         NONE(""),
306         CUSTOM("   ");
307 
308         String string;
309 
IndentKind(String indent)310         IndentKind(String indent) {
311             string = indent;
312         }
313     }
314 
315     class MyLog extends Log {
MyLog(Context ctx)316         MyLog(Context ctx) {
317             super(ctx);
318         }
319 
320         @Override
shouldReport(JavaFileObject jfo, int pos)321         protected boolean shouldReport(JavaFileObject jfo, int pos) {
322             return true;
323         }
324     }
325 
326     OutputKind outputKind;
327     ErrorKind errorKind;
328     MultilineKind multiKind;
329     MultilinePolicy multiPolicy;
330     PositionKind posKind;
331     XDiagsSource xdiagsSource;
332     XDiagsCompact xdiagsCompact;
333     CaretKind caretKind;
334     SourceLineKind sourceLineKind;
335     IndentKind summaryIndent;
336     IndentKind detailsIndent;
337     IndentKind sourceIndent;
338     IndentKind subdiagsIndent;
339 
T6769027(OutputKind outputKind, ErrorKind errorKind, MultilineKind multiKind, MultilinePolicy multiPolicy, PositionKind posKind, XDiagsSource xdiagsSource, XDiagsCompact xdiagsCompact, CaretKind caretKind, SourceLineKind sourceLineKind, IndentKind summaryIndent, IndentKind detailsIndent, IndentKind sourceIndent, IndentKind subdiagsIndent)340     T6769027(OutputKind outputKind, ErrorKind errorKind, MultilineKind multiKind,
341             MultilinePolicy multiPolicy, PositionKind posKind, XDiagsSource xdiagsSource,
342             XDiagsCompact xdiagsCompact, CaretKind caretKind, SourceLineKind sourceLineKind,
343             IndentKind summaryIndent, IndentKind detailsIndent, IndentKind sourceIndent,
344             IndentKind subdiagsIndent) {
345         this.outputKind = outputKind;
346         this.errorKind = errorKind;
347         this.multiKind = multiKind;
348         this.multiPolicy = multiPolicy;
349         this.posKind = posKind;
350         this.xdiagsSource = xdiagsSource;
351         this.xdiagsCompact = xdiagsCompact;
352         this.caretKind = caretKind;
353         this.sourceLineKind = sourceLineKind;
354         this.summaryIndent = summaryIndent;
355         this.detailsIndent = detailsIndent;
356         this.sourceIndent = sourceIndent;
357         this.subdiagsIndent = subdiagsIndent;
358     }
359 
run()360     public void run() {
361         Context ctx = new Context();
362         Options options = Options.instance(ctx);
363         outputKind.init(options);
364         multiPolicy.init(options);
365         xdiagsSource.init(options);
366         xdiagsCompact.init(options);
367         caretKind.init(options);
368         sourceLineKind.init(options);
369         String indentString = "";
370         indentString = (summaryIndent == IndentKind.CUSTOM) ? "3" : "0";
371         indentString += (detailsIndent == IndentKind.CUSTOM) ? "|3" : "|0";
372         indentString += (sourceIndent == IndentKind.CUSTOM) ? "|3" : "|0";
373         indentString += (subdiagsIndent == IndentKind.CUSTOM) ? "|3" : "|0";
374         options.put("diags.indent", indentString);
375         MyLog log = new MyLog(ctx);
376         JavacMessages messages = JavacMessages.instance(ctx);
377         messages.add(locale -> ResourceBundle.getBundle("tester", locale));
378         JCDiagnostic.Factory diags = JCDiagnostic.Factory.instance(ctx);
379         log.useSource(new MyFileObject("This is a source line"));
380         JCDiagnostic d = diags.error(null, log.currentSource(),
381             posKind.pos(),
382             errorKind.key(), "Hello!");
383         if (multiKind != MultilineKind.NONE) {
384             JCDiagnostic sub = diags.fragment(errorKind.key(), "Hello!");
385             if (multiKind.isNested())
386                 sub = new JCDiagnostic.MultilineDiagnostic(sub, List.of(sub));
387             List<JCDiagnostic> subdiags = multiKind.isDouble() ?
388                 List.of(sub, sub) :
389                 List.of(sub);
390             d = new JCDiagnostic.MultilineDiagnostic(d, subdiags);
391         }
392         String diag = log.getDiagnosticFormatter().format(d, messages.getCurrentLocale());
393         checkOutput(diag);
394     }
395 
main(String[] args)396     public static void main(String[] args) throws Exception {
397         for (OutputKind outputKind : OutputKind.values()) {
398             for (ErrorKind errKind : ErrorKind.values()) {
399                 for (MultilineKind multiKind : MultilineKind.values()) {
400                     for (MultilinePolicy multiPolicy : MultilinePolicy.values()) {
401                         for (PositionKind posKind : PositionKind.values()) {
402                             for (XDiagsSource xdiagsSource : XDiagsSource.values()) {
403                                 for (XDiagsCompact xdiagsCompact : XDiagsCompact.values()) {
404                                     for (CaretKind caretKind : CaretKind.values()) {
405                                         for (SourceLineKind sourceLineKind : SourceLineKind.values()) {
406                                             for (IndentKind summaryIndent : IndentKind.values()) {
407                                                 for (IndentKind detailsIndent : IndentKind.values()) {
408                                                     for (IndentKind sourceIndent : IndentKind.values()) {
409                                                         for (IndentKind subdiagsIndent : IndentKind.values()) {
410                                                             new T6769027(outputKind,
411                                                                 errKind,
412                                                                 multiKind,
413                                                                 multiPolicy,
414                                                                 posKind,
415                                                                 xdiagsSource,
416                                                                 xdiagsCompact,
417                                                                 caretKind,
418                                                                 sourceLineKind,
419                                                                 summaryIndent,
420                                                                 detailsIndent,
421                                                                 sourceIndent,
422                                                                 subdiagsIndent).run();
423                                                         }
424                                                     }
425                                                 }
426                                             }
427                                         }
428                                     }
429                                 }
430                             }
431                         }
432                     }
433                 }
434             }
435         }
436     }
437 
printInfo(String msg, String errorLine)438     void printInfo(String msg, String errorLine) {
439         String sep = "*********************************************************";
440         String desc = "raw=" + outputKind + " pos=" + posKind + " key=" + errorKind.key() +
441                 " multiline=" + multiKind +" multiPolicy=" + multiPolicy.value +
442                 " diags= " + java.util.Arrays.asList(xdiagsSource.flag, xdiagsCompact.flag) +
443                 " caret=" + caretKind + " sourcePosition=" + sourceLineKind +
444                 " summaryIndent=" + summaryIndent + " detailsIndent=" + detailsIndent +
445                 " sourceIndent=" + sourceIndent + " subdiagsIndent=" + subdiagsIndent;
446         System.err.println(sep);
447         System.err.println(desc);
448         System.err.println(sep);
449         System.err.println(msg);
450         System.err.println("Diagnostic formatting problem - expected diagnostic...\n" + errorLine);
451     }
452 
checkOutput(String msg)453     void checkOutput(String msg) {
454         boolean shouldPrintSource = posKind == PositionKind.POS &&
455                 xdiagsSource != XDiagsSource.NO_SOURCE &&
456                 (xdiagsSource == XDiagsSource.SOURCE ||
457                 outputKind == OutputKind.BASIC);
458         String errorLine = posKind.getOutput(outputKind) +
459                 errorKind.getOutput(outputKind, summaryIndent, detailsIndent);
460         if (xdiagsCompact != XDiagsCompact.COMPACT)
461             errorLine += multiKind.getOutput(outputKind, errorKind, multiPolicy,
462                     summaryIndent, detailsIndent, subdiagsIndent);
463         String[] lines = errorLine.split("\n");
464         if (xdiagsCompact == XDiagsCompact.COMPACT) {
465             errorLine = lines[0];
466             lines = new String[] {errorLine};
467         }
468         if (shouldPrintSource) {
469             if (sourceLineKind.isAfterSummary()) {
470                 String sep = "\n";
471                 if (lines.length == 1) {
472                     errorLine += "\n";
473                     sep = "";
474                 }
475                 errorLine = errorLine.replaceFirst("\n",
476                         Matcher.quoteReplacement(xdiagsSource.getOutput(caretKind, sourceIndent, outputKind) + sep));
477             }
478             else
479                 errorLine += xdiagsSource.getOutput(caretKind, sourceIndent, outputKind);
480         }
481 
482         if (!msg.equals(errorLine)) {
483             printInfo(msg, errorLine);
484             throw new AssertionError("errors were found");
485         }
486     }
487 
488 }
489