1 /* 2 * Copyright (c) 2002, 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. 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.SimpleTypeVisitor9; 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 and 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 final 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, "-llni", "-Xllni") { 224 void process(JavahTask task, String opt, String arg) { 225 task.llni = true; 226 } 227 }, 228 229 new HiddenOption(false, "-llnidouble") { 230 void process(JavahTask task, String opt, String arg) { 231 task.llni = true; 232 task.doubleAlign = true; 233 } 234 }, 235 236 new HiddenOption(false) { 237 boolean matches(String opt) { 238 return opt.startsWith("-XD"); 239 } 240 void process(JavahTask task, String opt, String arg) { 241 task.javac_extras.add(opt); 242 } 243 }, 244 }; 245 246 JavahTask() { 247 } 248 249 JavahTask(Writer out, 250 JavaFileManager fileManager, 251 DiagnosticListener<? super JavaFileObject> diagnosticListener, 252 Iterable<String> options, 253 Iterable<String> classes) { 254 this(); 255 this.log = getPrintWriterForWriter(out); 256 this.fileManager = fileManager; 257 this.diagnosticListener = diagnosticListener; 258 259 try { 260 handleOptions(options, false); 261 } catch (BadArgs e) { 262 throw new IllegalArgumentException(e.getMessage()); 263 } 264 265 this.classes = new ArrayList<>(); 266 if (classes != null) { 267 for (String classname: classes) { 268 classname.getClass(); // null-check 269 this.classes.add(classname); 270 } 271 } 272 } 273 274 public void setLocale(Locale locale) { 275 if (locale == null) 276 locale = Locale.getDefault(); 277 task_locale = locale; 278 } 279 280 public void setLog(PrintWriter log) { 281 this.log = log; 282 } 283 284 public void setLog(OutputStream s) { 285 setLog(getPrintWriterForStream(s)); 286 } 287 288 static PrintWriter getPrintWriterForStream(OutputStream s) { 289 return new PrintWriter(s, true); 290 } 291 292 static PrintWriter getPrintWriterForWriter(Writer w) { 293 if (w == null) 294 return getPrintWriterForStream(null); 295 else if (w instanceof PrintWriter) 296 return (PrintWriter) w; 297 else 298 return new PrintWriter(w, true); 299 } 300 301 public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) { 302 diagnosticListener = dl; 303 } 304 305 public void setDiagnosticListener(OutputStream s) { 306 setDiagnosticListener(getDiagnosticListenerForStream(s)); 307 } 308 309 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) { 310 return getDiagnosticListenerForWriter(getPrintWriterForStream(s)); 311 } 312 313 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) { 314 final PrintWriter pw = getPrintWriterForWriter(w); 315 return new DiagnosticListener<JavaFileObject> () { 316 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 317 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { 318 pw.print(getMessage("err.prefix")); 319 pw.print(" "); 320 } 321 pw.println(diagnostic.getMessage(null)); 322 } 323 }; 324 } 325 326 int run(String[] args) { 327 try { 328 handleOptions(args); 329 boolean ok = run(); 330 return ok ? 0 : 1; 331 } catch (BadArgs e) { 332 diagnosticListener.report(createDiagnostic(e.key, e.args)); 333 return 1; 334 } catch (InternalError e) { 335 diagnosticListener.report(createDiagnostic("err.internal.error", e.getMessage())); 336 return 1; 337 } catch (Util.Exit e) { 338 return e.exitValue; 339 } finally { 340 log.flush(); 341 } 342 } 343 344 public void handleOptions(String[] args) throws BadArgs { 345 handleOptions(Arrays.asList(args), true); 346 } 347 348 private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs { 349 if (log == null) { 350 log = getPrintWriterForStream(System.out); 351 if (diagnosticListener == null) 352 diagnosticListener = getDiagnosticListenerForStream(System.err); 353 } else { 354 if (diagnosticListener == null) 355 diagnosticListener = getDiagnosticListenerForWriter(log); 356 } 357 358 if (fileManager == null) 359 fileManager = getDefaultFileManager(diagnosticListener, log); 360 361 Iterator<String> iter = expandAtArgs(args).iterator(); 362 noArgs = !iter.hasNext(); 363 364 while (iter.hasNext()) { 365 String arg = iter.next(); 366 if (arg.startsWith("-")) 367 handleOption(arg, iter); 368 else if (allowClasses) { 369 if (classes == null) 370 classes = new ArrayList<>(); 371 classes.add(arg); 372 while (iter.hasNext()) 373 classes.add(iter.next()); 374 } else 375 throw new BadArgs("err.unknown.option", arg).showUsage(true); 376 } 377 378 if ((classes == null || classes.size() == 0) && 379 !(noArgs || help || version || fullVersion)) { 380 throw new BadArgs("err.no.classes.specified"); 381 } 382 383 if (jni && llni) 384 throw new BadArgs("jni.llni.mixed"); 385 386 if (odir != null && ofile != null) 387 throw new BadArgs("dir.file.mixed"); 388 } 389 390 private void handleOption(String name, Iterator<String> rest) throws BadArgs { 391 for (Option o: recognizedOptions) { 392 if (o.matches(name)) { 393 if (o.hasArg) { 394 if (rest.hasNext()) 395 o.process(this, name, rest.next()); 396 else 397 throw new BadArgs("err.missing.arg", name).showUsage(true); 398 } else 399 o.process(this, name, null); 400 401 if (o.ignoreRest()) { 402 while (rest.hasNext()) 403 rest.next(); 404 } 405 return; 406 } 407 } 408 409 if (fileManager.handleOption(name, rest)) 410 return; 411 412 throw new BadArgs("err.unknown.option", name).showUsage(true); 413 } 414 415 private Iterable<String> expandAtArgs(Iterable<String> args) throws BadArgs { 416 try { 417 List<String> l = new ArrayList<>(); 418 for (String arg: args) l.add(arg); 419 return Arrays.asList(CommandLine.parse(l.toArray(new String[l.size()]))); 420 } catch (FileNotFoundException e) { 421 throw new BadArgs("at.args.file.not.found", e.getLocalizedMessage()); 422 } catch (IOException e) { 423 throw new BadArgs("at.args.io.exception", e.getLocalizedMessage()); 424 } 425 } 426 427 public Boolean call() { 428 return run(); 429 } 430 431 public boolean run() throws Util.Exit { 432 433 Util util = new Util(log, diagnosticListener); 434 435 if (noArgs || help) { 436 showHelp(); 437 return help; // treat noArgs as an error for purposes of exit code 438 } 439 440 if (version || fullVersion) { 441 showVersion(fullVersion); 442 return true; 443 } 444 445 util.verbose = verbose; 446 447 Gen g; 448 449 if (llni) 450 g = new LLNI(doubleAlign, util); 451 else { 452 // if (stubs) 453 // throw new BadArgs("jni.no.stubs"); 454 g = new JNI(util); 455 } 456 457 if (ofile != null) { 458 if (!(fileManager instanceof StandardJavaFileManager)) { 459 diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-o")); 460 return false; 461 } 462 Iterable<? extends JavaFileObject> iter = 463 ((StandardJavaFileManager) fileManager).getJavaFileObjectsFromFiles(Collections.singleton(ofile)); 464 JavaFileObject fo = iter.iterator().next(); 465 g.setOutFile(fo); 466 } else { 467 if (odir != null) { 468 if (!(fileManager instanceof StandardJavaFileManager)) { 469 diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-d")); 470 return false; 471 } 472 473 if (!odir.exists()) 474 if (!odir.mkdirs()) 475 util.error("cant.create.dir", odir.toString()); 476 try { 477 ((StandardJavaFileManager) fileManager).setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(odir)); 478 } catch (IOException e) { 479 Object msg = e.getLocalizedMessage(); 480 if (msg == null) { 481 msg = e; 482 } 483 diagnosticListener.report(createDiagnostic("err.ioerror", odir, msg)); 484 return false; 485 } 486 } 487 g.setFileManager(fileManager); 488 } 489 490 /* 491 * Force set to false will turn off smarts about checking file 492 * content before writing. 493 */ 494 g.setForce(force); 495 496 if (fileManager instanceof JavahFileManager) 497 ((JavahFileManager) fileManager).setSymbolFileEnabled(false); 498 499 JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 500 List<String> opts = new ArrayList<>(); 501 opts.add("-proc:only"); 502 opts.addAll(javac_extras); 503 CompilationTask t = c.getTask(log, fileManager, diagnosticListener, opts, classes, null); 504 JavahProcessor p = new JavahProcessor(g); 505 t.setProcessors(Collections.singleton(p)); 506 507 boolean ok = t.call(); 508 if (p.exit != null) 509 throw new Util.Exit(p.exit); 510 return ok; 511 } 512 513 private List<File> pathToFiles(String path) { 514 List<File> files = new ArrayList<>(); 515 for (String f: path.split(File.pathSeparator)) { 516 if (f.length() > 0) 517 files.add(new File(f)); 518 } 519 return files; 520 } 521 522 static StandardJavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) { 523 return JavahFileManager.create(dl, log); 524 } 525 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", "-cp", "-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 } 543 544 private void showVersion(boolean full) { 545 log.println(version(full)); 546 } 547 548 private static final String versionRBName = "com.sun.tools.javah.resources.version"; 549 private static ResourceBundle versionRB; 550 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 } 570 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 } 576 577 public JavaFileObject getSource() { 578 return null; 579 } 580 581 public long getPosition() { 582 return Diagnostic.NOPOS; 583 } 584 585 public long getStartPosition() { 586 return Diagnostic.NOPOS; 587 } 588 589 public long getEndPosition() { 590 return Diagnostic.NOPOS; 591 } 592 593 public long getLineNumber() { 594 return Diagnostic.NOPOS; 595 } 596 597 public long getColumnNumber() { 598 return Diagnostic.NOPOS; 599 } 600 601 public String getCode() { 602 return key; 603 } 604 605 public String getMessage(Locale locale) { 606 return JavahTask.this.getMessage(locale, key, args); 607 } 608 609 }; 610 } 611 612 private String getMessage(String key, Object... args) { 613 return getMessage(task_locale, key, args); 614 } 615 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<>(); 622 } 623 624 if (locale == null) 625 locale = Locale.getDefault(); 626 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 } 636 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 } 644 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 Set<String> javac_extras = new LinkedHashSet<>(); 661 662 PrintWriter log; 663 JavaFileManager fileManager; 664 DiagnosticListener<? super JavaFileObject> diagnosticListener; 665 Locale task_locale; 666 Map<Locale, ResourceBundle> bundles; 667 668 private static final String progname = "javah"; 669 670 @SupportedAnnotationTypes("*") 671 class JavahProcessor extends AbstractProcessor { 672 private Messager messager; 673 674 JavahProcessor(Gen g) { 675 this.g = g; 676 } 677 678 @Override 679 public SourceVersion getSupportedSourceVersion() { 680 // since this is co-bundled with javac, we can assume it supports 681 // the latest source version 682 return SourceVersion.latest(); 683 } 684 685 @Override 686 public void init(ProcessingEnvironment pEnv) { 687 super.init(pEnv); 688 messager = processingEnv.getMessager(); 689 } 690 691 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 692 try { 693 Set<TypeElement> classes = getAllClasses(ElementFilter.typesIn(roundEnv.getRootElements())); 694 if (classes.size() > 0) { 695 checkMethodParameters(classes); 696 g.setProcessingEnvironment(processingEnv); 697 g.setClasses(classes); 698 g.run(); 699 } 700 } catch (CompletionFailure cf) { 701 messager.printMessage(ERROR, getMessage("class.not.found", cf.sym.getQualifiedName().toString())); 702 } catch (ClassNotFoundException cnfe) { 703 messager.printMessage(ERROR, getMessage("class.not.found", cnfe.getMessage())); 704 } catch (IOException ioe) { 705 messager.printMessage(ERROR, getMessage("io.exception", ioe.getMessage())); 706 } catch (Util.Exit e) { 707 exit = e; 708 } 709 710 return true; 711 } 712 713 private Set<TypeElement> getAllClasses(Set<? extends TypeElement> classes) { 714 Set<TypeElement> allClasses = new LinkedHashSet<>(); 715 getAllClasses0(classes, allClasses); 716 return allClasses; 717 } 718 719 private void getAllClasses0(Iterable<? extends TypeElement> classes, Set<TypeElement> allClasses) { 720 for (TypeElement c: classes) { 721 allClasses.add(c); 722 getAllClasses0(ElementFilter.typesIn(c.getEnclosedElements()), allClasses); 723 } 724 } 725 726 // 4942232: 727 // check that classes exist for all the parameters of native methods 728 private void checkMethodParameters(Set<TypeElement> classes) { 729 Types types = processingEnv.getTypeUtils(); 730 for (TypeElement te: classes) { 731 for (ExecutableElement ee: ElementFilter.methodsIn(te.getEnclosedElements())) { 732 for (VariableElement ve: ee.getParameters()) { 733 TypeMirror tm = ve.asType(); 734 checkMethodParametersVisitor.visit(tm, types); 735 } 736 } 737 } 738 } 739 740 private TypeVisitor<Void,Types> checkMethodParametersVisitor = 741 new SimpleTypeVisitor9<Void,Types>() { 742 @Override 743 public Void visitArray(ArrayType t, Types types) { 744 visit(t.getComponentType(), types); 745 return null; 746 } 747 @Override 748 public Void visitDeclared(DeclaredType t, Types types) { 749 t.asElement().getKind(); // ensure class exists 750 for (TypeMirror st: types.directSupertypes(t)) 751 visit(st, types); 752 return null; 753 } 754 }; 755 756 private Gen g; 757 private Util.Exit exit; 758 } 759 }