1 /* 2 * Copyright 2002-2009 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 26 package com.sun.tools.javah; 27 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; 46 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; 52 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; 64 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 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 API supported by Sun Microsystems. 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 } 95 96 BadArgs showUsage(boolean b) { 97 showUsage = b; 98 return this; 99 } 100 101 final String key; 102 final Object[] args; 103 boolean showUsage; 104 } 105 106 static abstract class Option { 107 Option(boolean hasArg, String... aliases) { 108 this.hasArg = hasArg; 109 this.aliases = aliases; 110 } 111 112 boolean isHidden() { 113 return false; 114 } 115 116 boolean matches(String opt) { 117 for (String a: aliases) { 118 if (a.equals(opt)) 119 return true; 120 } 121 return false; 122 } 123 124 boolean ignoreRest() { 125 return false; 126 } 127 128 abstract void process(JavahTask task, String opt, String arg) throws BadArgs; 129 130 final boolean hasArg; 131 final String[] aliases; 132 } 133 134 static abstract class HiddenOption extends Option { 135 HiddenOption(boolean hasArg, String... aliases) { 136 super(hasArg, aliases); 137 } 138 139 @Override 140 boolean isHidden() { 141 return true; 142 } 143 } 144 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 }, 151 152 new Option(true, "-d") { 153 void process(JavahTask task, String opt, String arg) { 154 task.odir = new File(arg); 155 } 156 }, 157 158 new HiddenOption(true, "-td") { 159 void process(JavahTask task, String opt, String arg) { 160 // ignored; for backwards compatibility 161 } 162 }, 163 164 new HiddenOption(false, "-stubs") { 165 void process(JavahTask task, String opt, String arg) { 166 // ignored; for backwards compatibility 167 } 168 }, 169 170 new Option(false, "-v", "-verbose") { 171 void process(JavahTask task, String opt, String arg) { 172 task.verbose = true; 173 } 174 }, 175 176 new Option(false, "-help", "--help", "-?") { 177 void process(JavahTask task, String opt, String arg) { 178 task.help = true; 179 } 180 }, 181 182 new HiddenOption(false, "-trace") { 183 void process(JavahTask task, String opt, String arg) { 184 task.trace = true; 185 } 186 }, 187 188 new Option(false, "-version") { 189 void process(JavahTask task, String opt, String arg) { 190 task.version = true; 191 } 192 }, 193 194 new HiddenOption(false, "-fullversion") { 195 void process(JavahTask task, String opt, String arg) { 196 task.fullVersion = true; 197 } 198 }, 199 200 new Option(false, "-jni") { 201 void process(JavahTask task, String opt, String arg) { 202 task.jni = true; 203 } 204 }, 205 206 new Option(false, "-force") { 207 void process(JavahTask task, String opt, String arg) { 208 task.force = true; 209 } 210 }, 211 212 new HiddenOption(false, "-Xnew") { 213 void process(JavahTask task, String opt, String arg) { 214 // we're already using the new javah 215 } 216 }, 217 218 new HiddenOption(false, "-old") { 219 void process(JavahTask task, String opt, String arg) { 220 task.old = true; 221 } 222 }, 223 224 new HiddenOption(false, "-llni", "-Xllni") { 225 void process(JavahTask task, String opt, String arg) { 226 task.llni = true; 227 } 228 }, 229 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 }; 237 238 JavahTask() { 239 } 240 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; 250 251 try { 252 handleOptions(options, false); 253 } catch (BadArgs e) { 254 throw new IllegalArgumentException(e.getMessage()); 255 } 256 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 } 265 266 public void setLocale(Locale locale) { 267 if (locale == null) 268 locale = Locale.getDefault(); 269 task_locale = locale; 270 } 271 272 public void setLog(PrintWriter log) { 273 this.log = log; 274 } 275 276 public void setLog(OutputStream s) { 277 setLog(getPrintWriterForStream(s)); 278 } 279 280 static PrintWriter getPrintWriterForStream(OutputStream s) { 281 return new PrintWriter(s, true); 282 } 283 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 } 292 293 public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) { 294 diagnosticListener = dl; 295 } 296 297 public void setDiagnosticListener(OutputStream s) { 298 setDiagnosticListener(getDiagnosticListenerForStream(s)); 299 } 300 301 private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) { 302 return getDiagnosticListenerForWriter(getPrintWriterForStream(s)); 303 } 304 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 } 317 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 } 333 334 public void handleOptions(String[] args) throws BadArgs { 335 handleOptions(Arrays.asList(args), true); 336 } 337 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 } 347 348 if (fileManager == null) 349 fileManager = getDefaultFileManager(diagnosticListener, log); 350 351 Iterator<String> iter = args.iterator(); 352 noArgs = !iter.hasNext(); 353 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 } 367 368 if ((classes == null || classes.size() == 0) && 369 !(noArgs || help || version || fullVersion)) { 370 throw new BadArgs("err.no.classes.specified"); 371 } 372 373 if (jni && llni) 374 throw new BadArgs("jni.llni.mixed"); 375 376 if (odir != null && ofile != null) 377 throw new BadArgs("dir.file.mixed"); 378 } 379 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); 390 391 if (o.ignoreRest()) { 392 while (rest.hasNext()) 393 rest.next(); 394 } 395 return; 396 } 397 } 398 399 if (fileManager.handleOption(name, rest)) 400 return; 401 402 throw new BadArgs("err.unknown.option", name).showUsage(true); 403 } 404 405 public Boolean call() { 406 return run(); 407 } 408 409 public boolean run() throws Util.Exit { 410 411 Util util = new Util(log, diagnosticListener); 412 413 if (noArgs || help) { 414 showHelp(); 415 return help; // treat noArgs as an error for purposes of exit code 416 } 417 418 if (version || fullVersion) { 419 showVersion(fullVersion); 420 return true; 421 } 422 423 util.verbose = verbose; 424 425 Gen g; 426 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 } 434 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 } 450 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 } 467 468 /* 469 * Force set to false will turn off smarts about checking file 470 * content before writing. 471 */ 472 g.setForce(force); 473 474 if (fileManager instanceof JavahFileManager) 475 ((JavahFileManager) fileManager).setIgnoreSymbolFile(true); 476 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)); 482 483 boolean ok = t.call(); 484 if (p.exit != null) 485 throw new Util.Exit(p.exit); 486 return ok; 487 } 488 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 } 496 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 } 505 506 static StandardJavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) { 507 return JavahFileManager.create(dl, log); 508 } 509 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 } 527 528 private void showVersion(boolean full) { 529 log.println(version(full ? "full" : "release")); 530 } 531 532 private static final String versionRBName = "com.sun.tools.javah.resources.version"; 533 private static ResourceBundle versionRB; 534 535 private String version(String key) { 536 // key=version: mm.nn.oo[-milestone] 537 // key=full: mm.mm.oo[-milestone]-build 538 if (versionRB == null) { 539 try { 540 versionRB = ResourceBundle.getBundle(versionRBName); 541 } catch (MissingResourceException e) { 542 return getMessage("version.resource.missing", System.getProperty("java.version")); 543 } 544 } 545 try { 546 return versionRB.getString(key); 547 } 548 catch (MissingResourceException e) { 549 return getMessage("version.unknown", System.getProperty("java.version")); 550 } 551 } 552 553 private Diagnostic<JavaFileObject> createDiagnostic(final String key, final Object... args) { 554 return new Diagnostic<JavaFileObject>() { 555 public Kind getKind() { 556 return Diagnostic.Kind.ERROR; 557 } 558 559 public JavaFileObject getSource() { 560 return null; 561 } 562 563 public long getPosition() { 564 return Diagnostic.NOPOS; 565 } 566 567 public long getStartPosition() { 568 return Diagnostic.NOPOS; 569 } 570 571 public long getEndPosition() { 572 return Diagnostic.NOPOS; 573 } 574 575 public long getLineNumber() { 576 return Diagnostic.NOPOS; 577 } 578 579 public long getColumnNumber() { 580 return Diagnostic.NOPOS; 581 } 582 583 public String getCode() { 584 return key; 585 } 586 587 public String getMessage(Locale locale) { 588 return JavahTask.this.getMessage(locale, key, args); 589 } 590 591 }; 592 593 } 594 private String getMessage(String key, Object... args) { 595 return getMessage(task_locale, key, args); 596 } 597 598 private String getMessage(Locale locale, String key, Object... args) { 599 if (bundles == null) { 600 // could make this a HashMap<Locale,SoftReference<ResourceBundle>> 601 // and for efficiency, keep a hard reference to the bundle for the task 602 // locale 603 bundles = new HashMap<Locale, ResourceBundle>(); 604 } 605 606 if (locale == null) 607 locale = Locale.getDefault(); 608 609 ResourceBundle b = bundles.get(locale); 610 if (b == null) { 611 try { 612 b = ResourceBundle.getBundle("com.sun.tools.javah.resources.l10n", locale); 613 bundles.put(locale, b); 614 } catch (MissingResourceException e) { 615 throw new InternalError("Cannot find javah resource bundle for locale " + locale, e); 616 } 617 } 618 619 try { 620 return MessageFormat.format(b.getString(key), args); 621 } catch (MissingResourceException e) { 622 return key; 623 //throw new InternalError(e, key); 624 } 625 } 626 627 File ofile; 628 File odir; 629 String bootcp; 630 String usercp; 631 List<String> classes; 632 boolean verbose; 633 boolean noArgs; 634 boolean help; 635 boolean trace; 636 boolean version; 637 boolean fullVersion; 638 boolean jni; 639 boolean llni; 640 boolean doubleAlign; 641 boolean force; 642 boolean old; 643 644 PrintWriter log; 645 JavaFileManager fileManager; 646 DiagnosticListener<? super JavaFileObject> diagnosticListener; 647 Locale task_locale; 648 Map<Locale, ResourceBundle> bundles; 649 650 private static final String progname = "javah"; 651 652 @SupportedAnnotationTypes("*") 653 @SupportedSourceVersion(SourceVersion.RELEASE_7) 654 class JavahProcessor extends AbstractProcessor { 655 JavahProcessor(Gen g) { 656 this.g = g; 657 } 658 659 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 660 Messager messager = processingEnv.getMessager(); 661 Set<TypeElement> classes = getAllClasses(ElementFilter.typesIn(roundEnv.getRootElements())); 662 if (classes.size() > 0) { 663 checkMethodParameters(classes); 664 g.setProcessingEnvironment(processingEnv); 665 g.setClasses(classes); 666 667 try { 668 g.run(); 669 } catch (ClassNotFoundException cnfe) { 670 messager.printMessage(Diagnostic.Kind.ERROR, getMessage("class.not.found", cnfe.getMessage())); 671 } catch (IOException ioe) { 672 messager.printMessage(Diagnostic.Kind.ERROR, getMessage("io.exception", ioe.getMessage())); 673 } catch (Util.Exit e) { 674 exit = e; 675 } 676 } 677 return true; 678 } 679 680 private Set<TypeElement> getAllClasses(Set<? extends TypeElement> classes) { 681 Set<TypeElement> allClasses = new LinkedHashSet<TypeElement>(); 682 getAllClasses0(classes, allClasses); 683 return allClasses; 684 } 685 686 private void getAllClasses0(Iterable<? extends TypeElement> classes, Set<TypeElement> allClasses) { 687 for (TypeElement c: classes) { 688 allClasses.add(c); 689 getAllClasses0(ElementFilter.typesIn(c.getEnclosedElements()), allClasses); 690 } 691 } 692 693 // 4942232: 694 // check that classes exist for all the parameters of native methods 695 private void checkMethodParameters(Set<TypeElement> classes) { 696 Types types = processingEnv.getTypeUtils(); 697 for (TypeElement te: classes) { 698 for (ExecutableElement ee: ElementFilter.methodsIn(te.getEnclosedElements())) { 699 for (VariableElement ve: ee.getParameters()) { 700 TypeMirror tm = ve.asType(); 701 checkMethodParametersVisitor.visit(tm, types); 702 } 703 } 704 } 705 } 706 707 private TypeVisitor<Void,Types> checkMethodParametersVisitor = 708 new SimpleTypeVisitor7<Void,Types>() { 709 @Override 710 public Void visitArray(ArrayType t, Types types) { 711 visit(t.getComponentType(), types); 712 return null; 713 } 714 @Override 715 public Void visitDeclared(DeclaredType t, Types types) { 716 t.asElement().getKind(); // ensure class exists 717 for (TypeMirror st: types.directSupertypes(t)) 718 visit(st, types); 719 return null; 720 } 721 }; 722 723 private Gen g; 724 private Util.Exit exit; 725 } 726 }