Mon, 27 Sep 2010 14:05:33 -0700
6890226: javah -version is broken
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.RoundEnvironment;
50 import javax.annotation.processing.SupportedAnnotationTypes;
51 import javax.annotation.processing.SupportedSourceVersion;
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;
75 /**
76 * Javah generates support files for native methods.
77 * Parse commandline options & Invokes javadoc to execute those commands.
78 *
79 * <p><b>This is NOT part of any supported API.
80 * If you write code that depends on this, you do so at your own
81 * risk. This code and its internal interfaces are subject to change
82 * or deletion without notice.</b></p>
83 *
84 * @author Sucheta Dambalkar
85 * @author Jonathan Gibbons
86 */
87 public class JavahTask implements NativeHeaderTool.NativeHeaderTask {
88 public class BadArgs extends Exception {
89 private static final long serialVersionUID = 1479361270874789045L;
90 BadArgs(String key, Object... args) {
91 super(JavahTask.this.getMessage(key, args));
92 this.key = key;
93 this.args = args;
94 }
96 BadArgs showUsage(boolean b) {
97 showUsage = b;
98 return this;
99 }
101 final String key;
102 final Object[] args;
103 boolean showUsage;
104 }
106 static abstract class Option {
107 Option(boolean hasArg, String... aliases) {
108 this.hasArg = hasArg;
109 this.aliases = aliases;
110 }
112 boolean isHidden() {
113 return false;
114 }
116 boolean matches(String opt) {
117 for (String a: aliases) {
118 if (a.equals(opt))
119 return true;
120 }
121 return false;
122 }
124 boolean ignoreRest() {
125 return false;
126 }
128 abstract void process(JavahTask task, String opt, String arg) throws BadArgs;
130 final boolean hasArg;
131 final String[] aliases;
132 }
134 static abstract class HiddenOption extends Option {
135 HiddenOption(boolean hasArg, String... aliases) {
136 super(hasArg, aliases);
137 }
139 @Override
140 boolean isHidden() {
141 return true;
142 }
143 }
145 static Option[] recognizedOptions = {
146 new Option(true, "-o") {
147 void process(JavahTask task, String opt, String arg) {
148 task.ofile = new File(arg);
149 }
150 },
152 new Option(true, "-d") {
153 void process(JavahTask task, String opt, String arg) {
154 task.odir = new File(arg);
155 }
156 },
158 new HiddenOption(true, "-td") {
159 void process(JavahTask task, String opt, String arg) {
160 // ignored; for backwards compatibility
161 }
162 },
164 new HiddenOption(false, "-stubs") {
165 void process(JavahTask task, String opt, String arg) {
166 // ignored; for backwards compatibility
167 }
168 },
170 new Option(false, "-v", "-verbose") {
171 void process(JavahTask task, String opt, String arg) {
172 task.verbose = true;
173 }
174 },
176 new Option(false, "-help", "--help", "-?") {
177 void process(JavahTask task, String opt, String arg) {
178 task.help = true;
179 }
180 },
182 new HiddenOption(false, "-trace") {
183 void process(JavahTask task, String opt, String arg) {
184 task.trace = true;
185 }
186 },
188 new Option(false, "-version") {
189 void process(JavahTask task, String opt, String arg) {
190 task.version = true;
191 }
192 },
194 new HiddenOption(false, "-fullversion") {
195 void process(JavahTask task, String opt, String arg) {
196 task.fullVersion = true;
197 }
198 },
200 new Option(false, "-jni") {
201 void process(JavahTask task, String opt, String arg) {
202 task.jni = true;
203 }
204 },
206 new Option(false, "-force") {
207 void process(JavahTask task, String opt, String arg) {
208 task.force = true;
209 }
210 },
212 new HiddenOption(false, "-Xnew") {
213 void process(JavahTask task, String opt, String arg) {
214 // we're already using the new javah
215 }
216 },
218 new HiddenOption(false, "-old") {
219 void process(JavahTask task, String opt, String arg) {
220 task.old = true;
221 }
222 },
224 new HiddenOption(false, "-llni", "-Xllni") {
225 void process(JavahTask task, String opt, String arg) {
226 task.llni = true;
227 }
228 },
230 new HiddenOption(false, "-llnidouble") {
231 void process(JavahTask task, String opt, String arg) {
232 task.llni = true;
233 task.doubleAlign = true;
234 }
235 },
236 };
238 JavahTask() {
239 }
241 JavahTask(Writer out,
242 JavaFileManager fileManager,
243 DiagnosticListener<? super JavaFileObject> diagnosticListener,
244 Iterable<String> options,
245 Iterable<String> classes) {
246 this();
247 this.log = getPrintWriterForWriter(out);
248 this.fileManager = fileManager;
249 this.diagnosticListener = diagnosticListener;
251 try {
252 handleOptions(options, false);
253 } catch (BadArgs e) {
254 throw new IllegalArgumentException(e.getMessage());
255 }
257 this.classes = new ArrayList<String>();
258 if (classes != null) {
259 for (String classname: classes) {
260 classname.getClass(); // null-check
261 this.classes.add(classname);
262 }
263 }
264 }
266 public void setLocale(Locale locale) {
267 if (locale == null)
268 locale = Locale.getDefault();
269 task_locale = locale;
270 }
272 public void setLog(PrintWriter log) {
273 this.log = log;
274 }
276 public void setLog(OutputStream s) {
277 setLog(getPrintWriterForStream(s));
278 }
280 static PrintWriter getPrintWriterForStream(OutputStream s) {
281 return new PrintWriter(s, true);
282 }
284 static PrintWriter getPrintWriterForWriter(Writer w) {
285 if (w == null)
286 return getPrintWriterForStream(null);
287 else if (w instanceof PrintWriter)
288 return (PrintWriter) w;
289 else
290 return new PrintWriter(w, true);
291 }
293 public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
294 diagnosticListener = dl;
295 }
297 public void setDiagnosticListener(OutputStream s) {
298 setDiagnosticListener(getDiagnosticListenerForStream(s));
299 }
301 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
302 return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
303 }
305 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) {
306 final PrintWriter pw = getPrintWriterForWriter(w);
307 return new DiagnosticListener<JavaFileObject> () {
308 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
309 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
310 pw.print(getMessage("err.prefix"));
311 pw.print(" ");
312 }
313 pw.println(diagnostic.getMessage(null));
314 }
315 };
316 }
318 int run(String[] args) {
319 try {
320 handleOptions(args);
321 boolean ok = run();
322 return ok ? 0 : 1;
323 } catch (BadArgs e) {
324 diagnosticListener.report(createDiagnostic(e.key, e.args));
325 return 1;
326 } catch (InternalError e) {
327 diagnosticListener.report(createDiagnostic("err.internal.error", e.getMessage()));
328 return 1;
329 } finally {
330 log.flush();
331 }
332 }
334 public void handleOptions(String[] args) throws BadArgs {
335 handleOptions(Arrays.asList(args), true);
336 }
338 private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs {
339 if (log == null) {
340 log = getPrintWriterForStream(System.out);
341 if (diagnosticListener == null)
342 diagnosticListener = getDiagnosticListenerForStream(System.err);
343 } else {
344 if (diagnosticListener == null)
345 diagnosticListener = getDiagnosticListenerForWriter(log);
346 }
348 if (fileManager == null)
349 fileManager = getDefaultFileManager(diagnosticListener, log);
351 Iterator<String> iter = args.iterator();
352 noArgs = !iter.hasNext();
354 while (iter.hasNext()) {
355 String arg = iter.next();
356 if (arg.startsWith("-"))
357 handleOption(arg, iter);
358 else if (allowClasses) {
359 if (classes == null)
360 classes = new ArrayList<String>();
361 classes.add(arg);
362 while (iter.hasNext())
363 classes.add(iter.next());
364 } else
365 throw new BadArgs("err.unknown.option", arg).showUsage(true);
366 }
368 if ((classes == null || classes.size() == 0) &&
369 !(noArgs || help || version || fullVersion)) {
370 throw new BadArgs("err.no.classes.specified");
371 }
373 if (jni && llni)
374 throw new BadArgs("jni.llni.mixed");
376 if (odir != null && ofile != null)
377 throw new BadArgs("dir.file.mixed");
378 }
380 private void handleOption(String name, Iterator<String> rest) throws BadArgs {
381 for (Option o: recognizedOptions) {
382 if (o.matches(name)) {
383 if (o.hasArg) {
384 if (rest.hasNext())
385 o.process(this, name, rest.next());
386 else
387 throw new BadArgs("err.missing.arg", name).showUsage(true);
388 } else
389 o.process(this, name, null);
391 if (o.ignoreRest()) {
392 while (rest.hasNext())
393 rest.next();
394 }
395 return;
396 }
397 }
399 if (fileManager.handleOption(name, rest))
400 return;
402 throw new BadArgs("err.unknown.option", name).showUsage(true);
403 }
405 public Boolean call() {
406 return run();
407 }
409 public boolean run() throws Util.Exit {
411 Util util = new Util(log, diagnosticListener);
413 if (noArgs || help) {
414 showHelp();
415 return help; // treat noArgs as an error for purposes of exit code
416 }
418 if (version || fullVersion) {
419 showVersion(fullVersion);
420 return true;
421 }
423 util.verbose = verbose;
425 Gen g;
427 if (llni)
428 g = new LLNI(doubleAlign, util);
429 else {
430 // if (stubs)
431 // throw new BadArgs("jni.no.stubs");
432 g = new JNI(util);
433 }
435 if (ofile != null) {
436 if (!(fileManager instanceof StandardJavaFileManager)) {
437 diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-o"));
438 return false;
439 }
440 Iterable<? extends JavaFileObject> iter =
441 ((StandardJavaFileManager) fileManager).getJavaFileObjectsFromFiles(Collections.singleton(ofile));
442 JavaFileObject fo = iter.iterator().next();
443 g.setOutFile(fo);
444 } else {
445 if (odir != null) {
446 if (!(fileManager instanceof StandardJavaFileManager)) {
447 diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-d"));
448 return false;
449 }
451 if (!odir.exists())
452 if (!odir.mkdirs())
453 util.error("cant.create.dir", odir.toString());
454 try {
455 ((StandardJavaFileManager) fileManager).setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(odir));
456 } catch (IOException e) {
457 Object msg = e.getLocalizedMessage();
458 if (msg == null) {
459 msg = e;
460 }
461 diagnosticListener.report(createDiagnostic("err.ioerror", odir, msg));
462 return false;
463 }
464 }
465 g.setFileManager(fileManager);
466 }
468 /*
469 * Force set to false will turn off smarts about checking file
470 * content before writing.
471 */
472 g.setForce(force);
474 if (fileManager instanceof JavahFileManager)
475 ((JavahFileManager) fileManager).setIgnoreSymbolFile(true);
477 JavaCompiler c = ToolProvider.getSystemJavaCompiler();
478 List<String> opts = Arrays.asList("-proc:only");
479 CompilationTask t = c.getTask(log, fileManager, diagnosticListener, opts, internalize(classes), null);
480 JavahProcessor p = new JavahProcessor(g);
481 t.setProcessors(Collections.singleton(p));
483 boolean ok = t.call();
484 if (p.exit != null)
485 throw new Util.Exit(p.exit);
486 return ok;
487 }
489 private List<String> internalize(List<String> classes) {
490 List<String> l = new ArrayList<String>();
491 for (String c: classes) {
492 l.add(c.replace('$', '.'));
493 }
494 return l;
495 }
497 private List<File> pathToFiles(String path) {
498 List<File> files = new ArrayList<File>();
499 for (String f: path.split(File.pathSeparator)) {
500 if (f.length() > 0)
501 files.add(new File(f));
502 }
503 return files;
504 }
506 static StandardJavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
507 return JavahFileManager.create(dl, log);
508 }
510 private void showHelp() {
511 log.println(getMessage("main.usage", progname));
512 for (Option o: recognizedOptions) {
513 if (o.isHidden())
514 continue;
515 String name = o.aliases[0].substring(1); // there must always be at least one name
516 log.println(getMessage("main.opt." + name));
517 }
518 String[] fmOptions = { "-classpath", "-bootclasspath" };
519 for (String o: fmOptions) {
520 if (fileManager.isSupportedOption(o) == -1)
521 continue;
522 String name = o.substring(1);
523 log.println(getMessage("main.opt." + name));
524 }
525 log.println(getMessage("main.usage.foot"));
526 }
528 private void showVersion(boolean full) {
529 log.println(version(full));
530 }
532 private static final String versionRBName = "com.sun.tools.javah.resources.version";
533 private static ResourceBundle versionRB;
535 private String version(boolean full) {
536 String msgKey = (full ? "javah.fullVersion" : "javah.version");
537 String versionKey = (full ? "full" : "release");
538 // versionKey=product: mm.nn.oo[-milestone]
539 // versionKey=full: mm.mm.oo[-milestone]-build
540 if (versionRB == null) {
541 try {
542 versionRB = ResourceBundle.getBundle(versionRBName);
543 } catch (MissingResourceException e) {
544 return getMessage("version.resource.missing", System.getProperty("java.version"));
545 }
546 }
547 try {
548 return getMessage(msgKey, "javah", versionRB.getString(versionKey));
549 }
550 catch (MissingResourceException e) {
551 return getMessage("version.unknown", System.getProperty("java.version"));
552 }
553 }
555 private Diagnostic<JavaFileObject> createDiagnostic(final String key, final Object... args) {
556 return new Diagnostic<JavaFileObject>() {
557 public Kind getKind() {
558 return Diagnostic.Kind.ERROR;
559 }
561 public JavaFileObject getSource() {
562 return null;
563 }
565 public long getPosition() {
566 return Diagnostic.NOPOS;
567 }
569 public long getStartPosition() {
570 return Diagnostic.NOPOS;
571 }
573 public long getEndPosition() {
574 return Diagnostic.NOPOS;
575 }
577 public long getLineNumber() {
578 return Diagnostic.NOPOS;
579 }
581 public long getColumnNumber() {
582 return Diagnostic.NOPOS;
583 }
585 public String getCode() {
586 return key;
587 }
589 public String getMessage(Locale locale) {
590 return JavahTask.this.getMessage(locale, key, args);
591 }
593 };
595 }
596 private String getMessage(String key, Object... args) {
597 return getMessage(task_locale, key, args);
598 }
600 private String getMessage(Locale locale, String key, Object... args) {
601 if (bundles == null) {
602 // could make this a HashMap<Locale,SoftReference<ResourceBundle>>
603 // and for efficiency, keep a hard reference to the bundle for the task
604 // locale
605 bundles = new HashMap<Locale, ResourceBundle>();
606 }
608 if (locale == null)
609 locale = Locale.getDefault();
611 ResourceBundle b = bundles.get(locale);
612 if (b == null) {
613 try {
614 b = ResourceBundle.getBundle("com.sun.tools.javah.resources.l10n", locale);
615 bundles.put(locale, b);
616 } catch (MissingResourceException e) {
617 throw new InternalError("Cannot find javah resource bundle for locale " + locale, e);
618 }
619 }
621 try {
622 return MessageFormat.format(b.getString(key), args);
623 } catch (MissingResourceException e) {
624 return key;
625 //throw new InternalError(e, key);
626 }
627 }
629 File ofile;
630 File odir;
631 String bootcp;
632 String usercp;
633 List<String> classes;
634 boolean verbose;
635 boolean noArgs;
636 boolean help;
637 boolean trace;
638 boolean version;
639 boolean fullVersion;
640 boolean jni;
641 boolean llni;
642 boolean doubleAlign;
643 boolean force;
644 boolean old;
646 PrintWriter log;
647 JavaFileManager fileManager;
648 DiagnosticListener<? super JavaFileObject> diagnosticListener;
649 Locale task_locale;
650 Map<Locale, ResourceBundle> bundles;
652 private static final String progname = "javah";
654 @SupportedAnnotationTypes("*")
655 @SupportedSourceVersion(SourceVersion.RELEASE_7)
656 class JavahProcessor extends AbstractProcessor {
657 JavahProcessor(Gen g) {
658 this.g = g;
659 }
661 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
662 Messager messager = processingEnv.getMessager();
663 Set<TypeElement> classes = getAllClasses(ElementFilter.typesIn(roundEnv.getRootElements()));
664 if (classes.size() > 0) {
665 checkMethodParameters(classes);
666 g.setProcessingEnvironment(processingEnv);
667 g.setClasses(classes);
669 try {
670 g.run();
671 } catch (ClassNotFoundException cnfe) {
672 messager.printMessage(Diagnostic.Kind.ERROR, getMessage("class.not.found", cnfe.getMessage()));
673 } catch (IOException ioe) {
674 messager.printMessage(Diagnostic.Kind.ERROR, getMessage("io.exception", ioe.getMessage()));
675 } catch (Util.Exit e) {
676 exit = e;
677 }
678 }
679 return true;
680 }
682 private Set<TypeElement> getAllClasses(Set<? extends TypeElement> classes) {
683 Set<TypeElement> allClasses = new LinkedHashSet<TypeElement>();
684 getAllClasses0(classes, allClasses);
685 return allClasses;
686 }
688 private void getAllClasses0(Iterable<? extends TypeElement> classes, Set<TypeElement> allClasses) {
689 for (TypeElement c: classes) {
690 allClasses.add(c);
691 getAllClasses0(ElementFilter.typesIn(c.getEnclosedElements()), allClasses);
692 }
693 }
695 // 4942232:
696 // check that classes exist for all the parameters of native methods
697 private void checkMethodParameters(Set<TypeElement> classes) {
698 Types types = processingEnv.getTypeUtils();
699 for (TypeElement te: classes) {
700 for (ExecutableElement ee: ElementFilter.methodsIn(te.getEnclosedElements())) {
701 for (VariableElement ve: ee.getParameters()) {
702 TypeMirror tm = ve.asType();
703 checkMethodParametersVisitor.visit(tm, types);
704 }
705 }
706 }
707 }
709 private TypeVisitor<Void,Types> checkMethodParametersVisitor =
710 new SimpleTypeVisitor7<Void,Types>() {
711 @Override
712 public Void visitArray(ArrayType t, Types types) {
713 visit(t.getComponentType(), types);
714 return null;
715 }
716 @Override
717 public Void visitDeclared(DeclaredType t, Types types) {
718 t.asElement().getKind(); // ensure class exists
719 for (TypeMirror st: types.directSupertypes(t))
720 visit(st, types);
721 return null;
722 }
723 };
725 private Gen g;
726 private Util.Exit exit;
727 }
728 }