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