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