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