1 /* 2 * Copyright (c) 2003, 2005, 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 sun.rmi.rmic.newrmic; 27 28 import com.sun.javadoc.ClassDoc; 29 import com.sun.javadoc.RootDoc; 30 import java.io.File; 31 import java.io.FileNotFoundException; 32 import java.io.IOException; 33 import java.io.OutputStream; 34 import java.io.PrintStream; 35 import java.io.PrintWriter; 36 import java.lang.reflect.Constructor; 37 import java.lang.reflect.InvocationTargetException; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 import sun.rmi.rmic.newrmic.jrmp.JrmpGenerator; 46 import sun.tools.util.CommandLine; 47 48 /** 49 * The rmic front end. This class contains the "main" method for rmic 50 * command line invocation. 51 * 52 * A Main instance contains the stream to output error messages and 53 * other diagnostics to. 54 * 55 * An rmic compilation batch (for example, one rmic command line 56 * invocation) is executed by invoking the "compile" method of a Main 57 * instance. 58 * 59 * WARNING: The contents of this source file are not part of any 60 * supported API. Code that depends on them does so at its own risk: 61 * they are subject to change or removal without notice. 62 * 63 * NOTE: If and when there is a J2SE API for invoking SDK tools, this 64 * class should be updated to support that API. 65 * 66 * NOTE: This class is the front end for a "new" rmic implementation, 67 * which uses javadoc and the doclet API for reading class files and 68 * javac for compiling generated source files. This implementation is 69 * incomplete: it lacks any CORBA-based back end implementations, and 70 * thus the command line options "-idl", "-iiop", and their related 71 * options are not yet supported. The front end for the "old", 72 * oldjavac-based rmic implementation is sun.rmi.rmic.Main. 73 * 74 * @author Peter Jones 75 **/ 76 public class Main { 77 78 /* 79 * Implementation note: 80 * 81 * In order to use the doclet API to read class files, much of 82 * this implementation of rmic executes as a doclet within an 83 * invocation of javadoc. This class is used as the doclet class 84 * for such javadoc invocations, via its static "start" and 85 * "optionLength" methods. There is one javadoc invocation per 86 * rmic compilation batch. 87 * 88 * The only guaranteed way to pass data to a doclet through a 89 * javadoc invocation is through doclet-specific options on the 90 * javadoc "command line". Rather than passing numerous pieces of 91 * individual data in string form as javadoc options, we use a 92 * single doclet-specific option ("-batchID") to pass a numeric 93 * identifier that uniquely identifies the rmic compilation batch 94 * that the javadoc invocation is for, and that identifier can 95 * then be used as a key in a global table to retrieve an object 96 * containing all of batch-specific data (rmic command line 97 * arguments, etc.). 98 */ 99 100 /** guards "batchCount" */ 101 private static final Object batchCountLock = new Object(); 102 103 /** number of batches run; used to generated batch IDs */ 104 private static long batchCount = 0; 105 106 /** maps batch ID to batch data */ 107 private static final Map<Long,Batch> batchTable = 108 Collections.synchronizedMap(new HashMap<Long,Batch>()); 109 110 /** stream to output error messages and other diagnostics to */ 111 private final PrintStream out; 112 113 /** name of this program, to use in error messages */ 114 private final String program; 115 116 /** 117 * Command line entry point. 118 **/ 119 public static void main(String[] args) { 120 Main rmic = new Main(System.err, "rmic"); 121 System.exit(rmic.compile(args) ? 0 : 1); 122 } 123 124 /** 125 * Creates a Main instance that writes output to the specified 126 * stream. The specified program name is used in error messages. 127 **/ 128 public Main(OutputStream out, String program) { 129 this.out = out instanceof PrintStream ? 130 (PrintStream) out : new PrintStream(out); 131 this.program = program; 132 } 133 134 /** 135 * Compiles a batch of input classes, as given by the specified 136 * command line arguments. Protocol-specific generators are 137 * determined by the choice options on the command line. Returns 138 * true if successful, or false if an error occurred. 139 * 140 * NOTE: This method is retained for transitional consistency with 141 * previous implementations. 142 **/ 143 public boolean compile(String[] args) { 144 long startTime = System.currentTimeMillis(); 145 146 long batchID; 147 synchronized (batchCountLock) { 148 batchID = batchCount++; // assign batch ID 149 } 150 151 // process command line 152 Batch batch = parseArgs(args); 153 if (batch == null) { 154 return false; // terminate if error occurred 155 } 156 157 /* 158 * With the batch data retrievable in the global table, run 159 * javadoc to continue the rest of the batch's compliation as 160 * a doclet. 161 */ 162 boolean status; 163 try { 164 batchTable.put(batchID, batch); 165 status = invokeJavadoc(batch, batchID); 166 } finally { 167 batchTable.remove(batchID); 168 } 169 170 if (batch.verbose) { 171 long deltaTime = System.currentTimeMillis() - startTime; 172 output(Resources.getText("rmic.done_in", 173 Long.toString(deltaTime))); 174 } 175 176 return status; 177 } 178 179 /** 180 * Prints the specified string to the output stream of this Main 181 * instance. 182 **/ 183 public void output(String msg) { 184 out.println(msg); 185 } 186 187 /** 188 * Prints an error message to the output stream of this Main 189 * instance. The first argument is used as a key in rmic's 190 * resource bundle, and the rest of the arguments are used as 191 * arguments in the formatting of the resource string. 192 **/ 193 public void error(String msg, String... args) { 194 output(Resources.getText(msg, args)); 195 } 196 197 /** 198 * Prints rmic's usage message to the output stream of this Main 199 * instance. 200 * 201 * This method is public so that it can be used by the "parseArgs" 202 * methods of Generator implementations. 203 **/ 204 public void usage() { 205 error("rmic.usage", program); 206 } 207 208 /** 209 * Processes rmic command line arguments. Returns a Batch object 210 * representing the command line arguments if successful, or null 211 * if an error occurred. Processed elements of the args array are 212 * set to null. 213 **/ 214 private Batch parseArgs(String[] args) { 215 Batch batch = new Batch(); 216 217 /* 218 * Pre-process command line for @file arguments. 219 */ 220 try { 221 args = CommandLine.parse(args); 222 } catch (FileNotFoundException e) { 223 error("rmic.cant.read", e.getMessage()); 224 return null; 225 } catch (IOException e) { 226 e.printStackTrace(out); 227 return null; 228 } 229 230 for (int i = 0; i < args.length; i++) { 231 232 if (args[i] == null) { 233 // already processed by a generator 234 continue; 235 236 } else if (args[i].equals("-Xnew")) { 237 // we're already using the "new" implementation 238 args[i] = null; 239 240 } else if (args[i].equals("-show")) { 241 // obselete: fail 242 error("rmic.option.unsupported", args[i]); 243 usage(); 244 return null; 245 246 } else if (args[i].equals("-O")) { 247 // obselete: warn but tolerate 248 error("rmic.option.unsupported", args[i]); 249 args[i] = null; 250 251 } else if (args[i].equals("-debug")) { 252 // obselete: warn but tolerate 253 error("rmic.option.unsupported", args[i]); 254 args[i] = null; 255 256 } else if (args[i].equals("-depend")) { 257 // obselete: warn but tolerate 258 // REMIND: should this fail instead? 259 error("rmic.option.unsupported", args[i]); 260 args[i] = null; 261 262 } else if (args[i].equals("-keep") || 263 args[i].equals("-keepgenerated")) 264 { 265 batch.keepGenerated = true; 266 args[i] = null; 267 268 } else if (args[i].equals("-g")) { 269 batch.debug = true; 270 args[i] = null; 271 272 } else if (args[i].equals("-nowarn")) { 273 batch.noWarn = true; 274 args[i] = null; 275 276 } else if (args[i].equals("-nowrite")) { 277 batch.noWrite = true; 278 args[i] = null; 279 280 } else if (args[i].equals("-verbose")) { 281 batch.verbose = true; 282 args[i] = null; 283 284 } else if (args[i].equals("-Xnocompile")) { 285 batch.noCompile = true; 286 batch.keepGenerated = true; 287 args[i] = null; 288 289 } else if (args[i].equals("-bootclasspath")) { 290 if ((i + 1) >= args.length) { 291 error("rmic.option.requires.argument", args[i]); 292 usage(); 293 return null; 294 } 295 if (batch.bootClassPath != null) { 296 error("rmic.option.already.seen", args[i]); 297 usage(); 298 return null; 299 } 300 args[i] = null; 301 batch.bootClassPath = args[++i]; 302 assert batch.bootClassPath != null; 303 args[i] = null; 304 305 } else if (args[i].equals("-extdirs")) { 306 if ((i + 1) >= args.length) { 307 error("rmic.option.requires.argument", args[i]); 308 usage(); 309 return null; 310 } 311 if (batch.extDirs != null) { 312 error("rmic.option.already.seen", args[i]); 313 usage(); 314 return null; 315 } 316 args[i] = null; 317 batch.extDirs = args[++i]; 318 assert batch.extDirs != null; 319 args[i] = null; 320 321 } else if (args[i].equals("-classpath")) { 322 if ((i + 1) >= args.length) { 323 error("rmic.option.requires.argument", args[i]); 324 usage(); 325 return null; 326 } 327 if (batch.classPath != null) { 328 error("rmic.option.already.seen", args[i]); 329 usage(); 330 return null; 331 } 332 args[i] = null; 333 batch.classPath = args[++i]; 334 assert batch.classPath != null; 335 args[i] = null; 336 337 } else if (args[i].equals("-d")) { 338 if ((i + 1) >= args.length) { 339 error("rmic.option.requires.argument", args[i]); 340 usage(); 341 return null; 342 } 343 if (batch.destDir != null) { 344 error("rmic.option.already.seen", args[i]); 345 usage(); 346 return null; 347 } 348 args[i] = null; 349 batch.destDir = new File(args[++i]); 350 assert batch.destDir != null; 351 args[i] = null; 352 if (!batch.destDir.exists()) { 353 error("rmic.no.such.directory", batch.destDir.getPath()); 354 usage(); 355 return null; 356 } 357 358 } else if (args[i].equals("-v1.1") || 359 args[i].equals("-vcompat") || 360 args[i].equals("-v1.2")) 361 { 362 Generator gen = new JrmpGenerator(); 363 batch.generators.add(gen); 364 // JrmpGenerator only requires base BatchEnvironment class 365 if (!gen.parseArgs(args, this)) { 366 return null; 367 } 368 369 } else if (args[i].equalsIgnoreCase("-iiop")) { 370 error("rmic.option.unimplemented", args[i]); 371 return null; 372 373 // Generator gen = new IiopGenerator(); 374 // batch.generators.add(gen); 375 // if (!batch.envClass.isAssignableFrom(gen.envClass())) { 376 // error("rmic.cannot.use.both", 377 // batch.envClass.getName(), gen.envClass().getName()); 378 // return null; 379 // } 380 // batch.envClass = gen.envClass(); 381 // if (!gen.parseArgs(args, this)) { 382 // return null; 383 // } 384 385 } else if (args[i].equalsIgnoreCase("-idl")) { 386 error("rmic.option.unimplemented", args[i]); 387 return null; 388 389 // see implementation sketch above 390 391 } else if (args[i].equalsIgnoreCase("-xprint")) { 392 error("rmic.option.unimplemented", args[i]); 393 return null; 394 395 // see implementation sketch above 396 } 397 } 398 399 /* 400 * At this point, all that remains non-null in the args 401 * array are input class names or illegal options. 402 */ 403 for (int i = 0; i < args.length; i++) { 404 if (args[i] != null) { 405 if (args[i].startsWith("-")) { 406 error("rmic.no.such.option", args[i]); 407 usage(); 408 return null; 409 } else { 410 batch.classes.add(args[i]); 411 } 412 } 413 } 414 if (batch.classes.isEmpty()) { 415 usage(); 416 return null; 417 } 418 419 /* 420 * If options did not specify at least one protocol-specific 421 * generator, then JRMP is the default. 422 */ 423 if (batch.generators.isEmpty()) { 424 batch.generators.add(new JrmpGenerator()); 425 } 426 return batch; 427 } 428 429 /** 430 * Doclet class entry point. 431 **/ 432 public static boolean start(RootDoc rootDoc) { 433 434 /* 435 * Find batch ID among javadoc options, and retrieve 436 * corresponding batch data from global table. 437 */ 438 long batchID = -1; 439 for (String[] option : rootDoc.options()) { 440 if (option[0].equals("-batchID")) { 441 try { 442 batchID = Long.parseLong(option[1]); 443 } catch (NumberFormatException e) { 444 throw new AssertionError(e); 445 } 446 } 447 } 448 Batch batch = batchTable.get(batchID); 449 assert batch != null; 450 451 /* 452 * Construct batch environment using class agreed upon by 453 * generator implementations. 454 */ 455 BatchEnvironment env; 456 try { 457 Constructor<? extends BatchEnvironment> cons = 458 batch.envClass.getConstructor(new Class[] { RootDoc.class }); 459 env = cons.newInstance(rootDoc); 460 } catch (NoSuchMethodException e) { 461 throw new AssertionError(e); 462 } catch (IllegalAccessException e) { 463 throw new AssertionError(e); 464 } catch (InstantiationException e) { 465 throw new AssertionError(e); 466 } catch (InvocationTargetException e) { 467 throw new AssertionError(e); 468 } 469 470 env.setVerbose(batch.verbose); 471 472 /* 473 * Determine the destination directory (the top of the package 474 * hierarchy) for the output of this batch; if no destination 475 * directory was specified on the command line, then the 476 * default is the current working directory. 477 */ 478 File destDir = batch.destDir; 479 if (destDir == null) { 480 destDir = new File(System.getProperty("user.dir")); 481 } 482 483 /* 484 * Run each input class through each generator. 485 */ 486 for (String inputClassName : batch.classes) { 487 ClassDoc inputClass = rootDoc.classNamed(inputClassName); 488 try { 489 for (Generator gen : batch.generators) { 490 gen.generate(env, inputClass, destDir); 491 } 492 } catch (NullPointerException e) { 493 /* 494 * We assume that this means that some class that was 495 * needed (perhaps even a bootstrap class) was not 496 * found, and that javadoc has already reported this 497 * as an error. There is nothing for us to do here 498 * but try to continue with the next input class. 499 * 500 * REMIND: More explicit error checking throughout 501 * would be preferable, however. 502 */ 503 } 504 } 505 506 /* 507 * Compile any generated source files, if configured to do so. 508 */ 509 boolean status = true; 510 List<File> generatedFiles = env.generatedFiles(); 511 if (!batch.noCompile && !batch.noWrite && !generatedFiles.isEmpty()) { 512 status = batch.enclosingMain().invokeJavac(batch, generatedFiles); 513 } 514 515 /* 516 * Delete any generated source files, if configured to do so. 517 */ 518 if (!batch.keepGenerated) { 519 for (File file : generatedFiles) { 520 file.delete(); 521 } 522 } 523 524 return status; 525 } 526 527 /** 528 * Doclet class method that indicates that this doclet class 529 * recognizes (only) the "-batchID" option on the javadoc command 530 * line, and that the "-batchID" option comprises two arguments on 531 * the javadoc command line. 532 **/ 533 public static int optionLength(String option) { 534 if (option.equals("-batchID")) { 535 return 2; 536 } else { 537 return 0; 538 } 539 } 540 541 /** 542 * Runs the javadoc tool to invoke this class as a doclet, passing 543 * command line options derived from the specified batch data and 544 * indicating the specified batch ID. 545 * 546 * NOTE: This method currently uses a J2SE-internal API to run 547 * javadoc. If and when there is a J2SE API for invoking SDK 548 * tools, this method should be updated to use that API instead. 549 **/ 550 private boolean invokeJavadoc(Batch batch, long batchID) { 551 List<String> javadocArgs = new ArrayList<String>(); 552 553 // include all types, regardless of language-level access 554 javadocArgs.add("-private"); 555 556 // inputs are class names, not source files 557 javadocArgs.add("-Xclasses"); 558 559 // reproduce relevant options from rmic invocation 560 if (batch.verbose) { 561 javadocArgs.add("-verbose"); 562 } 563 if (batch.bootClassPath != null) { 564 javadocArgs.add("-bootclasspath"); 565 javadocArgs.add(batch.bootClassPath); 566 } 567 if (batch.extDirs != null) { 568 javadocArgs.add("-extdirs"); 569 javadocArgs.add(batch.extDirs); 570 } 571 if (batch.classPath != null) { 572 javadocArgs.add("-classpath"); 573 javadocArgs.add(batch.classPath); 574 } 575 576 // specify batch ID 577 javadocArgs.add("-batchID"); 578 javadocArgs.add(Long.toString(batchID)); 579 580 /* 581 * Run javadoc on union of rmic input classes and all 582 * generators' bootstrap classes, so that they will all be 583 * available to the doclet code. 584 */ 585 Set<String> classNames = new HashSet<String>(); 586 for (Generator gen : batch.generators) { 587 classNames.addAll(gen.bootstrapClassNames()); 588 } 589 classNames.addAll(batch.classes); 590 for (String s : classNames) { 591 javadocArgs.add(s); 592 } 593 594 // run javadoc with our program name and output stream 595 int status = com.sun.tools.javadoc.Main.execute( 596 program, 597 new PrintWriter(out, true), 598 new PrintWriter(out, true), 599 new PrintWriter(out, true), 600 this.getClass().getName(), // doclet class is this class 601 javadocArgs.toArray(new String[javadocArgs.size()])); 602 return status == 0; 603 } 604 605 /** 606 * Runs the javac tool to compile the specified source files, 607 * passing command line options derived from the specified batch 608 * data. 609 * 610 * NOTE: This method currently uses a J2SE-internal API to run 611 * javac. If and when there is a J2SE API for invoking SDK tools, 612 * this method should be updated to use that API instead. 613 **/ 614 private boolean invokeJavac(Batch batch, List<File> files) { 615 List<String> javacArgs = new ArrayList<String>(); 616 617 // rmic never wants to display javac warnings 618 javacArgs.add("-nowarn"); 619 620 // reproduce relevant options from rmic invocation 621 if (batch.debug) { 622 javacArgs.add("-g"); 623 } 624 if (batch.verbose) { 625 javacArgs.add("-verbose"); 626 } 627 if (batch.bootClassPath != null) { 628 javacArgs.add("-bootclasspath"); 629 javacArgs.add(batch.bootClassPath); 630 } 631 if (batch.extDirs != null) { 632 javacArgs.add("-extdirs"); 633 javacArgs.add(batch.extDirs); 634 } 635 if (batch.classPath != null) { 636 javacArgs.add("-classpath"); 637 javacArgs.add(batch.classPath); 638 } 639 640 /* 641 * For now, rmic still always produces class files that have a 642 * class file format version compatible with JDK 1.1. 643 */ 644 javacArgs.add("-source"); 645 javacArgs.add("1.3"); 646 javacArgs.add("-target"); 647 javacArgs.add("1.1"); 648 649 // add source files to compile 650 for (File file : files) { 651 javacArgs.add(file.getPath()); 652 } 653 654 // run javac with our output stream 655 int status = com.sun.tools.javac.Main.compile( 656 javacArgs.toArray(new String[javacArgs.size()]), 657 new PrintWriter(out, true)); 658 return status == 0; 659 } 660 661 /** 662 * The data for an rmic compliation batch: the processed command 663 * line arguments. 664 **/ 665 private class Batch { 666 boolean keepGenerated = false; // -keep or -keepgenerated 667 boolean debug = false; // -g 668 boolean noWarn = false; // -nowarn 669 boolean noWrite = false; // -nowrite 670 boolean verbose = false; // -verbose 671 boolean noCompile = false; // -Xnocompile 672 String bootClassPath = null; // -bootclasspath 673 String extDirs = null; // -extdirs 674 String classPath = null; // -classpath 675 File destDir = null; // -d 676 List<Generator> generators = new ArrayList<Generator>(); 677 Class<? extends BatchEnvironment> envClass = BatchEnvironment.class; 678 List<String> classes = new ArrayList<String>(); 679 680 Batch() { } 681 682 /** 683 * Returns the Main instance for this batch. 684 **/ 685 Main enclosingMain() { 686 return Main.this; 687 } 688 } 689 }