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