1 /*
2  * Copyright (c) 2010, 2014, 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 import java.io.*;
25 import java.util.*;
26 import java.util.List;
27 import javax.tools.*;
28 
29 import com.sun.tools.javac.api.*;
30 import com.sun.tools.javac.api.DiagnosticFormatter.Configuration.DiagnosticPart;
31 import com.sun.tools.javac.api.Formattable.LocalizedString;
32 import com.sun.tools.javac.code.Flags.Flag;
33 import com.sun.tools.javac.code.Kinds.KindName;
34 import com.sun.tools.javac.code.*;
35 import com.sun.tools.javac.file.*;
36 import com.sun.tools.javac.main.Main;
37 import com.sun.tools.javac.main.JavaCompiler;
38 import com.sun.tools.javac.parser.Tokens.TokenKind;
39 import com.sun.tools.javac.util.*;
40 import com.sun.tools.javac.util.AbstractDiagnosticFormatter.SimpleConfiguration;
41 import javax.lang.model.SourceVersion;
42 
43 /**
44  * Compiler factory for instances of Example.Compiler that use custom
45  * DiagnosticFormatter and Messages objects to track the types of args
46  * when when localizing diagnostics.
47  * The compiler objects only support "output" mode, not "check" mode.
48  */
49 class ArgTypeCompilerFactory implements Example.Compiler.Factory {
50     // Same code as Example.Compiler.DefaultFactory, but the names resolve differently
getCompiler(List<String> opts, boolean verbose)51     public Example.Compiler getCompiler(List<String> opts, boolean verbose) {
52         String first;
53         String[] rest;
54         if (opts == null || opts.isEmpty()) {
55             first = null;
56             rest = new String[0];
57         } else {
58             first = opts.get(0);
59             rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]);
60         }
61         if (first == null || first.equals("jsr199"))
62             return new Jsr199Compiler(verbose, rest);
63         else if (first.equals("simple"))
64             return new SimpleCompiler(verbose);
65         else if (first.equals("backdoor"))
66             return new BackdoorCompiler(verbose);
67         else
68             throw new IllegalArgumentException(first);
69     }
70 
71     /**
72      * Compile using the JSR 199 API.  The diagnostics generated are
73      * scanned for resource keys.   Not all diagnostic keys are generated
74      * via the JSR 199 API -- for example, rich diagnostics are not directly
75      * accessible, and some diagnostics generated by the file manager may
76      * not be generated (for example, the JSR 199 file manager does not see
77      * -Xlint:path).
78      */
79     static class Jsr199Compiler extends Example.Compiler {
80         List<String> fmOpts;
81 
Jsr199Compiler(boolean verbose, String... args)82         Jsr199Compiler(boolean verbose, String... args) {
83             super(verbose);
84             for (int i = 0; i < args.length; i++) {
85                 String arg = args[i];
86                 if (arg.equals("-filemanager") && (i + 1 < args.length)) {
87                     fmOpts = Arrays.asList(args[++i].split(","));
88                 } else
89                     throw new IllegalArgumentException(arg);
90             }
91         }
92 
93         @Override
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)94         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
95             assert out != null && keys == null;
96 
97             if (verbose)
98                 System.err.println("run_jsr199: " + opts + " " + files);
99 
100             JavacTool tool = JavacTool.create();
101 
102             StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null);
103             try {
104                 if (fmOpts != null)
105                     fm = new FileManager(fm, fmOpts);
106 
107                 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
108 
109                 Context c = initContext();
110                 JavacTaskImpl t = (JavacTaskImpl) tool.getTask(out, fm, null, opts, null, fos, c);
111                 return t.call();
112             } finally {
113                 close(fm);
114             }
115         }
116     }
117 
118 
119     /**
120      * Run the test using the standard simple entry point.
121      */
122     static class SimpleCompiler extends Example.Compiler {
SimpleCompiler(boolean verbose)123         SimpleCompiler(boolean verbose) {
124             super(verbose);
125         }
126 
127         @Override
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)128         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
129             assert out != null && keys == null;
130 
131             if (verbose)
132                 System.err.println("run_simple: " + opts + " " + files);
133 
134             List<String> args = new ArrayList<String>();
135 
136             args.addAll(opts);
137             for (File f: files)
138                 args.add(f.getPath());
139 
140             Main main = new Main("javac", out);
141             Context c = initContext();
142             JavacFileManager.preRegister(c); // can't create it until Log has been set up
143 
144             try {
145                 Main.Result result = main.compile(args.toArray(new String[args.size()]), c);
146 
147                 return result.isOK();
148             } finally {
149                 close(c.get(JavaFileManager.class));
150             }
151         }
152     }
153 
154     static class BackdoorCompiler extends Example.Compiler {
BackdoorCompiler(boolean verbose)155         BackdoorCompiler(boolean verbose) {
156             super(verbose);
157         }
158 
159         @Override
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)160         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
161             assert out != null && keys == null;
162 
163             if (verbose)
164                 System.err.println("run_simple: " + opts + " " + files);
165 
166             List<String> args = new ArrayList<String>(opts);
167             for (File f: files)
168                 args.add(f.getPath());
169 
170             Context c = initContext();
171             JavacFileManager.preRegister(c); // can't create it until Log has been set up
172 
173             try {
174                 Main m = new Main("javac", out);
175                 Main.Result result = m.compile(args.toArray(new String[args.size()]), c);
176 
177                 return result.isOK();
178             } finally {
179                 close(c.get(JavaFileManager.class));
180             }
181         }
182     }
183 
initContext()184     private static Context initContext() {
185         Context context = new Context();
186         ArgTypeMessages.preRegister(context);
187         Options options = Options.instance(context);
188         options.addListener(() -> {
189             Log log = Log.instance(context);
190             log.setDiagnosticFormatter(new ArgTypeDiagnosticFormatter(options));
191         });
192         return context;
193     }
194 
195     // <editor-fold defaultstate="collapsed" desc="Custom Javac components">
196 
197     /**
198      * Diagnostic formatter which reports formats a diag as a series of lines
199      * containing a key, and a possibly empty set of descriptive strings for the
200      * arg types.
201      */
202     static class ArgTypeDiagnosticFormatter extends AbstractDiagnosticFormatter {
203 
ArgTypeDiagnosticFormatter(Options options)204         ArgTypeDiagnosticFormatter(Options options) {
205             super(null, new SimpleConfiguration(options,
206                     EnumSet.of(DiagnosticPart.SUMMARY,
207                     DiagnosticPart.DETAILS,
208                     DiagnosticPart.SUBDIAGNOSTICS)));
209         }
210 
211         @Override
formatDiagnostic(JCDiagnostic d, Locale locale)212         protected String formatDiagnostic(JCDiagnostic d, Locale locale) {
213             return formatMessage(d, locale);
214         }
215 
216         @Override
formatMessage(JCDiagnostic d, Locale l)217         public String formatMessage(JCDiagnostic d, Locale l) {
218             StringBuilder buf = new StringBuilder();
219             formatMessage(d, buf);
220             return buf.toString();
221         }
222 
formatMessage(JCDiagnostic d, StringBuilder buf)223         private void formatMessage(JCDiagnostic d, StringBuilder buf) {
224             String key = d.getCode();
225             Object[] args = d.getArgs();
226             // report the primary arg types, without recursing into diag fragments
227             buf.append(getKeyArgsString(key, args));
228             // report details for any diagnostic fragments
229             for (Object arg: args) {
230                 if (arg instanceof JCDiagnostic) {
231                     buf.append("\n");
232                     formatMessage((JCDiagnostic) arg, buf);
233                 }
234             }
235             // report details for any subdiagnostics
236             for (String s: formatSubdiagnostics(d, null)) {
237                 buf.append("\n");
238                 buf.append(s);
239             }
240         }
241 
242         @Override
isRaw()243         public boolean isRaw() {
244             return true;
245         }
246     }
247 
248     /**
249      * Diagnostic formatter which "localizes" a message as a line
250      * containing a key, and a possibly empty set of descriptive strings for the
251      * arg types.
252      */
253     static class ArgTypeMessages extends JavacMessages {
preRegister(Context context)254         static void preRegister(Context context) {
255             context.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() {
256                 public JavacMessages make(Context c) {
257                     return new ArgTypeMessages(c) {
258                         @Override
259                         public String getLocalizedString(Locale l, String key, Object... args) {
260                             return getKeyArgsString(key, args);
261                         }
262                     };
263                 }
264             });
265         }
266 
ArgTypeMessages(Context context)267         ArgTypeMessages(Context context) {
268             super(context);
269         }
270     }
271 
272     /**
273      * Utility method to generate a string for key and args
274      */
getKeyArgsString(String key, Object... args)275     static String getKeyArgsString(String key, Object... args) {
276         StringBuilder buf = new StringBuilder();
277         buf.append(key);
278         String sep = ": ";
279         for (Object o : args) {
280             buf.append(sep);
281             buf.append(getArgTypeOrStringValue(o));
282             sep = ", ";
283         }
284         return buf.toString();
285     }
286 
287     static boolean showStringValues = false;
288 
getArgTypeOrStringValue(Object o)289     static String getArgTypeOrStringValue(Object o) {
290         if (showStringValues && o instanceof String)
291             return "\"" + o + "\"";
292         return getArgType(o);
293     }
294 
getArgType(Object o)295     static String getArgType(Object o) {
296         if (o == null)
297             return "null";
298         if (o instanceof Name)
299             return "name";
300         if (o instanceof Boolean)
301             return "boolean";
302         if (o instanceof Integer)
303             return "number";
304         if (o instanceof String)
305             return "string";
306         if (o instanceof Flag)
307             return "modifier";
308         if (o instanceof KindName)
309             return "symbol kind";
310         if (o instanceof TokenKind)
311             return "token";
312         if (o instanceof Symbol)
313             return "symbol";
314         if (o instanceof Type)
315             return "type";
316         if (o instanceof List) {
317             List<?> l = (List<?>) o;
318             if (l.isEmpty())
319                 return "list";
320             else
321                 return "list of " + getArgType(l.get(0));
322         }
323         if (o instanceof ListBuffer)
324             return getArgType(((ListBuffer) o).toList());
325         if (o instanceof Set) {
326             Set<?> s = (Set<?>) o;
327             if (s.isEmpty())
328                 return "set";
329             else
330                 return "set of " + getArgType(s.iterator().next());
331         }
332         if (o instanceof SourceVersion)
333             return "source version";
334         if (o instanceof FileObject || o instanceof File)
335             return "file name";
336         if (o instanceof JCDiagnostic)
337             return "message segment";
338         if (o instanceof LocalizedString)
339             return "message segment";  // only instance is "no arguments"
340         String s = o.getClass().getSimpleName();
341         return (s.isEmpty() ? o.getClass().getName() : s);
342     }
343 
344     // </editor-fold>
345 
346 }
347