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 }