Tue, 12 Oct 2010 13:19:47 -0700
4942232: missing param class processes without error
Reviewed-by: darcy
1 /*
2 * Copyright (c) 2002, 2010, 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 */
26 package com.sun.tools.javah;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.io.PrintWriter;
32 import java.io.Writer;
33 import java.text.MessageFormat;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.LinkedHashSet;
40 import java.util.List;
41 import java.util.Locale;
42 import java.util.Map;
43 import java.util.MissingResourceException;
44 import java.util.ResourceBundle;
45 import java.util.Set;
47 import javax.annotation.processing.AbstractProcessor;
48 import javax.annotation.processing.Messager;
49 import javax.annotation.processing.ProcessingEnvironment;
50 import javax.annotation.processing.RoundEnvironment;
51 import javax.annotation.processing.SupportedAnnotationTypes;
53 import javax.lang.model.SourceVersion;
54 import javax.lang.model.element.ExecutableElement;
55 import javax.lang.model.element.TypeElement;
56 import javax.lang.model.element.VariableElement;
57 import javax.lang.model.type.ArrayType;
58 import javax.lang.model.type.DeclaredType;
59 import javax.lang.model.type.TypeMirror;
60 import javax.lang.model.type.TypeVisitor;
61 import javax.lang.model.util.ElementFilter;
62 import javax.lang.model.util.SimpleTypeVisitor7;
63 import javax.lang.model.util.Types;
65 import javax.tools.Diagnostic;
66 import javax.tools.DiagnosticListener;
67 import javax.tools.JavaCompiler;
68 import javax.tools.JavaCompiler.CompilationTask;
69 import javax.tools.JavaFileManager;
70 import javax.tools.JavaFileObject;
71 import javax.tools.StandardJavaFileManager;
72 import javax.tools.StandardLocation;
73 import javax.tools.ToolProvider;
74 import static javax.tools.Diagnostic.Kind.*;
76 import com.sun.tools.javac.code.Symbol.CompletionFailure;
78 /**
79 * Javah generates support files for native methods.
80 * Parse commandline options & Invokes javadoc to execute those commands.
81 *
82 * <p><b>This is NOT part of any supported API.
83 * If you write code that depends on this, you do so at your own
84 * risk. This code and its internal interfaces are subject to change
85 * or deletion without notice.</b></p>
86 *
87 * @author Sucheta Dambalkar
88 * @author Jonathan Gibbons
89 */
90 public class JavahTask implements NativeHeaderTool.NativeHeaderTask {
91 public class BadArgs extends Exception {
92 private static final long serialVersionUID = 1479361270874789045L;
93 BadArgs(String key, Object... args) {
94 super(JavahTask.this.getMessage(key, args));
95 this.key = key;
96 this.args = args;
97 }
99 BadArgs showUsage(boolean b) {
100 showUsage = b;
101 return this;
102 }
104 final String key;
105 final Object[] args;
106 boolean showUsage;
107 }
109 static abstract class Option {
110 Option(boolean hasArg, String... aliases) {
111 this.hasArg = hasArg;
112 this.aliases = aliases;
113 }
115 boolean isHidden() {
116 return false;
117 }
119 boolean matches(String opt) {
120 for (String a: aliases) {
121 if (a.equals(opt))
122 return true;
123 }
124 return false;
125 }
127 boolean ignoreRest() {
128 return false;
129 }
131 abstract void process(JavahTask task, String opt, String arg) throws BadArgs;
133 final boolean hasArg;
134 final String[] aliases;
135 }
137 static abstract class HiddenOption extends Option {
138 HiddenOption(boolean hasArg, String... aliases) {
139 super(hasArg, aliases);
140 }
142 @Override
143 boolean isHidden() {
144 return true;
145 }
146 }
148 static Option[] recognizedOptions = {
149 new Option(true, "-o") {
150 void process(JavahTask task, String opt, String arg) {
151 task.ofile = new File(arg);
152 }
153 },
155 new Option(true, "-d") {
156 void process(JavahTask task, String opt, String arg) {
157 task.odir = new File(arg);
158 }
159 },
161 new HiddenOption(true, "-td") {
162 void process(JavahTask task, String opt, String arg) {
163 // ignored; for backwards compatibility
164 }
165 },
167 new HiddenOption(false, "-stubs") {
168 void process(JavahTask task, String opt, String arg) {
169 // ignored; for backwards compatibility
170 }
171 },
173 new Option(false, "-v", "-verbose") {
174 void process(JavahTask task, String opt, String arg) {
175 task.verbose = true;
176 }
177 },
179 new Option(false, "-h", "-help", "--help", "-?") {
180 void process(JavahTask task, String opt, String arg) {
181 task.help = true;
182 }
183 },
185 new HiddenOption(false, "-trace") {
186 void process(JavahTask task, String opt, String arg) {
187 task.trace = true;
188 }
189 },
191 new Option(false, "-version") {
192 void process(JavahTask task, String opt, String arg) {
193 task.version = true;
194 }
195 },
197 new HiddenOption(false, "-fullversion") {
198 void process(JavahTask task, String opt, String arg) {
199 task.fullVersion = true;
200 }
201 },
203 new Option(false, "-jni") {
204 void process(JavahTask task, String opt, String arg) {
205 task.jni = true;
206 }
207 },
209 new Option(false, "-force") {
210 void process(JavahTask task, String opt, String arg) {
211 task.force = true;
212 }
213 },
215 new HiddenOption(false, "-Xnew") {
216 void process(JavahTask task, String opt, String arg) {
217 // we're already using the new javah
218 }
219 },
221 new HiddenOption(false, "-old") {
222 void process(JavahTask task, String opt, String arg) {
223 task.old = true;
224 }
225 },
227 new HiddenOption(false, "-llni", "-Xllni") {
228 void process(JavahTask task, String opt, String arg) {
229 task.llni = true;
230 }
231 },
233 new HiddenOption(false, "-llnidouble") {
234 void process(JavahTask task, String opt, String arg) {
235 task.llni = true;
236 task.doubleAlign = true;
237 }
238 },
240 new HiddenOption(false) {
241 boolean matches(String opt) {
242 return opt.startsWith("-XD");
243 }
244 void process(JavahTask task, String opt, String arg) {
245 task.javac_extras.add(opt);
246 }
247 },
248 };
250 JavahTask() {
251 }
253 JavahTask(Writer out,
254 JavaFileManager fileManager,
255 DiagnosticListener<? super JavaFileObject> diagnosticListener,
256 Iterable<String> options,
257 Iterable<String> classes) {
258 this();
259 this.log = getPrintWriterForWriter(out);
260 this.fileManager = fileManager;
261 this.diagnosticListener = diagnosticListener;
263 try {
264 handleOptions(options, false);
265 } catch (BadArgs e) {
266 throw new IllegalArgumentException(e.getMessage());
267 }
269 this.classes = new ArrayList<String>();
270 if (classes != null) {
271 for (String classname: classes) {
272 classname.getClass(); // null-check
273 this.classes.add(classname);
274 }
275 }
276 }
278 public void setLocale(Locale locale) {
279 if (locale == null)
280 locale = Locale.getDefault();
281 task_locale = locale;
282 }
284 public void setLog(PrintWriter log) {
285 this.log = log;
286 }
288 public void setLog(OutputStream s) {
289 setLog(getPrintWriterForStream(s));
290 }
292 static PrintWriter getPrintWriterForStream(OutputStream s) {
293 return new PrintWriter(s, true);
294 }
296 static PrintWriter getPrintWriterForWriter(Writer w) {
297 if (w == null)
298 return getPrintWriterForStream(null);
299 else if (w instanceof PrintWriter)
300 return (PrintWriter) w;
301 else
302 return new PrintWriter(w, true);
303 }
305 public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
306 diagnosticListener = dl;
307 }
309 public void setDiagnosticListener(OutputStream s) {
310 setDiagnosticListener(getDiagnosticListenerForStream(s));
311 }
313 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
314 return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
315 }
317 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) {
318 final PrintWriter pw = getPrintWriterForWriter(w);
319 return new DiagnosticListener<JavaFileObject> () {
320 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
321 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
322 pw.print(getMessage("err.prefix"));
323 pw.print(" ");
324 }
325 pw.println(diagnostic.getMessage(null));
326 }
327 };
328 }
330 int run(String[] args) {
331 try {
332 handleOptions(args);
333 boolean ok = run();
334 return ok ? 0 : 1;
335 } catch (BadArgs e) {
336 diagnosticListener.report(createDiagnostic(e.key, e.args));
337 return 1;
338 } catch (InternalError e) {
339 diagnosticListener.report(createDiagnostic("err.internal.error", e.getMessage()));
340 return 1;
341 } catch (Util.Exit e) {
342 return e.exitValue;
343 } finally {
344 log.flush();
345 }
346 }
348 public void handleOptions(String[] args) throws BadArgs {
349 handleOptions(Arrays.asList(args), true);
350 }
352 private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs {
353 if (log == null) {
354 log = getPrintWriterForStream(System.out);
355 if (diagnosticListener == null)
356 diagnosticListener = getDiagnosticListenerForStream(System.err);
357 } else {
358 if (diagnosticListener == null)
359 diagnosticListener = getDiagnosticListenerForWriter(log);
360 }
362 if (fileManager == null)
363 fileManager = getDefaultFileManager(diagnosticListener, log);
365 Iterator<String> iter = args.iterator();
366 noArgs = !iter.hasNext();
368 while (iter.hasNext()) {
369 String arg = iter.next();
370 if (arg.startsWith("-"))
371 handleOption(arg, iter);
372 else if (allowClasses) {
373 if (classes == null)
374 classes = new ArrayList<String>();
375 classes.add(arg);
376 while (iter.hasNext())
377 classes.add(iter.next());
378 } else
379 throw new BadArgs("err.unknown.option", arg).showUsage(true);
380 }
382 if ((classes == null || classes.size() == 0) &&
383 !(noArgs || help || version || fullVersion)) {
384 throw new BadArgs("err.no.classes.specified");
385 }
387 if (jni && llni)
388 throw new BadArgs("jni.llni.mixed");
390 if (odir != null && ofile != null)
391 throw new BadArgs("dir.file.mixed");
392 }
394 private void handleOption(String name, Iterator<String> rest) throws BadArgs {
395 for (Option o: recognizedOptions) {
396 if (o.matches(name)) {
397 if (o.hasArg) {
398 if (rest.hasNext())
399 o.process(this, name, rest.next());
400 else
401 throw new BadArgs("err.missing.arg", name).showUsage(true);
402 } else
403 o.process(this, name, null);
405 if (o.ignoreRest()) {
406 while (rest.hasNext())
407 rest.next();
408 }
409 return;
410 }
411 }
413 if (fileManager.handleOption(name, rest))
414 return;
416 throw new BadArgs("err.unknown.option", name).showUsage(true);
417 }
419 public Boolean call() {
420 return run();
421 }
423 public boolean run() throws Util.Exit {
425 Util util = new Util(log, diagnosticListener);
427 if (noArgs || help) {
428 showHelp();
429 return help; // treat noArgs as an error for purposes of exit code
430 }
432 if (version || fullVersion) {
433 showVersion(fullVersion);
434 return true;
435 }
437 util.verbose = verbose;
439 Gen g;
441 if (llni)
442 g = new LLNI(doubleAlign, util);
443 else {
444 // if (stubs)
445 // throw new BadArgs("jni.no.stubs");
446 g = new JNI(util);
447 }
449 if (ofile != null) {
450 if (!(fileManager instanceof StandardJavaFileManager)) {
451 diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-o"));
452 return false;
453 }
454 Iterable<? extends JavaFileObject> iter =
455 ((StandardJavaFileManager) fileManager).getJavaFileObjectsFromFiles(Collections.singleton(ofile));
456 JavaFileObject fo = iter.iterator().next();
457 g.setOutFile(fo);
458 } else {
459 if (odir != null) {
460 if (!(fileManager instanceof StandardJavaFileManager)) {
461 diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-d"));
462 return false;
463 }
465 if (!odir.exists())
466 if (!odir.mkdirs())
467 util.error("cant.create.dir", odir.toString());
468 try {
469 ((StandardJavaFileManager) fileManager).setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(odir));
470 } catch (IOException e) {
471 Object msg = e.getLocalizedMessage();
472 if (msg == null) {
473 msg = e;
474 }
475 diagnosticListener.report(createDiagnostic("err.ioerror", odir, msg));
476 return false;
477 }
478 }
479 g.setFileManager(fileManager);
480 }
482 /*
483 * Force set to false will turn off smarts about checking file
484 * content before writing.
485 */
486 g.setForce(force);
488 if (fileManager instanceof JavahFileManager)
489 ((JavahFileManager) fileManager).setIgnoreSymbolFile(true);
491 JavaCompiler c = ToolProvider.getSystemJavaCompiler();
492 List<String> opts = new ArrayList<String>();
493 opts.add("-proc:only");
494 opts.addAll(javac_extras);
495 CompilationTask t = c.getTask(log, fileManager, diagnosticListener, opts, internalize(classes), null);
496 JavahProcessor p = new JavahProcessor(g);
497 t.setProcessors(Collections.singleton(p));
499 boolean ok = t.call();
500 if (p.exit != null)
501 throw new Util.Exit(p.exit);
502 return ok;
503 }
505 private List<String> internalize(List<String> classes) {
506 List<String> l = new ArrayList<String>();
507 for (String c: classes) {
508 l.add(c.replace('$', '.'));
509 }
510 return l;
511 }
513 private List<File> pathToFiles(String path) {
514 List<File> files = new ArrayList<File>();
515 for (String f: path.split(File.pathSeparator)) {
516 if (f.length() > 0)
517 files.add(new File(f));
518 }
519 return files;
520 }
522 static StandardJavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
523 return JavahFileManager.create(dl, log);
524 }
526 private void showHelp() {
527 log.println(getMessage("main.usage", progname));
528 for (Option o: recognizedOptions) {
529 if (o.isHidden())
530 continue;
531 String name = o.aliases[0].substring(1); // there must always be at least one name
532 log.println(getMessage("main.opt." + name));
533 }
534 String[] fmOptions = { "-classpath", "-bootclasspath" };
535 for (String o: fmOptions) {
536 if (fileManager.isSupportedOption(o) == -1)
537 continue;
538 String name = o.substring(1);
539 log.println(getMessage("main.opt." + name));
540 }
541 log.println(getMessage("main.usage.foot"));
542 }
544 private void showVersion(boolean full) {
545 log.println(version(full));
546 }
548 private static final String versionRBName = "com.sun.tools.javah.resources.version";
549 private static ResourceBundle versionRB;
551 private String version(boolean full) {
552 String msgKey = (full ? "javah.fullVersion" : "javah.version");
553 String versionKey = (full ? "full" : "release");
554 // versionKey=product: mm.nn.oo[-milestone]
555 // versionKey=full: mm.mm.oo[-milestone]-build
556 if (versionRB == null) {
557 try {
558 versionRB = ResourceBundle.getBundle(versionRBName);
559 } catch (MissingResourceException e) {
560 return getMessage("version.resource.missing", System.getProperty("java.version"));
561 }
562 }
563 try {
564 return getMessage(msgKey, "javah", versionRB.getString(versionKey));
565 }
566 catch (MissingResourceException e) {
567 return getMessage("version.unknown", System.getProperty("java.version"));
568 }
569 }
571 private Diagnostic<JavaFileObject> createDiagnostic(final String key, final Object... args) {
572 return new Diagnostic<JavaFileObject>() {
573 public Kind getKind() {
574 return Diagnostic.Kind.ERROR;
575 }
577 public JavaFileObject getSource() {
578 return null;
579 }
581 public long getPosition() {
582 return Diagnostic.NOPOS;
583 }
585 public long getStartPosition() {
586 return Diagnostic.NOPOS;
587 }
589 public long getEndPosition() {
590 return Diagnostic.NOPOS;
591 }
593 public long getLineNumber() {
594 return Diagnostic.NOPOS;
595 }
597 public long getColumnNumber() {
598 return Diagnostic.NOPOS;
599 }
601 public String getCode() {
602 return key;
603 }
605 public String getMessage(Locale locale) {
606 return JavahTask.this.getMessage(locale, key, args);
607 }
609 };
611 }
612 private String getMessage(String key, Object... args) {
613 return getMessage(task_locale, key, args);
614 }
616 private String getMessage(Locale locale, String key, Object... args) {
617 if (bundles == null) {
618 // could make this a HashMap<Locale,SoftReference<ResourceBundle>>
619 // and for efficiency, keep a hard reference to the bundle for the task
620 // locale
621 bundles = new HashMap<Locale, ResourceBundle>();
622 }
624 if (locale == null)
625 locale = Locale.getDefault();
627 ResourceBundle b = bundles.get(locale);
628 if (b == null) {
629 try {
630 b = ResourceBundle.getBundle("com.sun.tools.javah.resources.l10n", locale);
631 bundles.put(locale, b);
632 } catch (MissingResourceException e) {
633 throw new InternalError("Cannot find javah resource bundle for locale " + locale, e);
634 }
635 }
637 try {
638 return MessageFormat.format(b.getString(key), args);
639 } catch (MissingResourceException e) {
640 return key;
641 //throw new InternalError(e, key);
642 }
643 }
645 File ofile;
646 File odir;
647 String bootcp;
648 String usercp;
649 List<String> classes;
650 boolean verbose;
651 boolean noArgs;
652 boolean help;
653 boolean trace;
654 boolean version;
655 boolean fullVersion;
656 boolean jni;
657 boolean llni;
658 boolean doubleAlign;
659 boolean force;
660 boolean old;
661 Set<String> javac_extras = new LinkedHashSet<String>();
663 PrintWriter log;
664 JavaFileManager fileManager;
665 DiagnosticListener<? super JavaFileObject> diagnosticListener;
666 Locale task_locale;
667 Map<Locale, ResourceBundle> bundles;
669 private static final String progname = "javah";
671 @SupportedAnnotationTypes("*")
672 class JavahProcessor extends AbstractProcessor {
673 private Messager messager;
675 JavahProcessor(Gen g) {
676 this.g = g;
677 }
679 @Override
680 public SourceVersion getSupportedSourceVersion() {
681 // since this is co-bundled with javac, we can assume it supports
682 // the latest source version
683 return SourceVersion.latest();
684 }
686 @Override
687 public void init(ProcessingEnvironment pEnv) {
688 super.init(pEnv);
689 messager = processingEnv.getMessager();
690 }
692 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
693 try {
694 Set<TypeElement> classes = getAllClasses(ElementFilter.typesIn(roundEnv.getRootElements()));
695 if (classes.size() > 0) {
696 checkMethodParameters(classes);
697 g.setProcessingEnvironment(processingEnv);
698 g.setClasses(classes);
699 g.run();
700 }
701 } catch (CompletionFailure cf) {
702 messager.printMessage(ERROR, getMessage("class.not.found", cf.sym.getQualifiedName().toString()));
703 } catch (ClassNotFoundException cnfe) {
704 messager.printMessage(ERROR, getMessage("class.not.found", cnfe.getMessage()));
705 } catch (IOException ioe) {
706 messager.printMessage(ERROR, getMessage("io.exception", ioe.getMessage()));
707 } catch (Util.Exit e) {
708 exit = e;
709 }
711 return true;
712 }
714 private Set<TypeElement> getAllClasses(Set<? extends TypeElement> classes) {
715 Set<TypeElement> allClasses = new LinkedHashSet<TypeElement>();
716 getAllClasses0(classes, allClasses);
717 return allClasses;
718 }
720 private void getAllClasses0(Iterable<? extends TypeElement> classes, Set<TypeElement> allClasses) {
721 for (TypeElement c: classes) {
722 allClasses.add(c);
723 getAllClasses0(ElementFilter.typesIn(c.getEnclosedElements()), allClasses);
724 }
725 }
727 // 4942232:
728 // check that classes exist for all the parameters of native methods
729 private void checkMethodParameters(Set<TypeElement> classes) {
730 Types types = processingEnv.getTypeUtils();
731 for (TypeElement te: classes) {
732 for (ExecutableElement ee: ElementFilter.methodsIn(te.getEnclosedElements())) {
733 for (VariableElement ve: ee.getParameters()) {
734 TypeMirror tm = ve.asType();
735 checkMethodParametersVisitor.visit(tm, types);
736 }
737 }
738 }
739 }
741 private TypeVisitor<Void,Types> checkMethodParametersVisitor =
742 new SimpleTypeVisitor7<Void,Types>() {
743 @Override
744 public Void visitArray(ArrayType t, Types types) {
745 visit(t.getComponentType(), types);
746 return null;
747 }
748 @Override
749 public Void visitDeclared(DeclaredType t, Types types) {
750 t.asElement().getKind(); // ensure class exists
751 for (TypeMirror st: types.directSupertypes(t))
752 visit(st, types);
753 return null;
754 }
755 };
757 private Gen g;
758 private Util.Exit exit;
759 }
760 }