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