1 /*
   2  * Copyright (c) 1997, 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 jdk.javadoc.internal.tool;
  27 
  28 import java.io.File;
  29 import java.io.FileNotFoundException;
  30 import java.io.IOException;
  31 import java.io.PrintWriter;
  32 import java.lang.reflect.Method;
  33 import java.nio.file.Path;
  34 import java.text.BreakIterator;
  35 import java.util.ArrayList;
  36 import java.util.Arrays;
  37 import java.util.Collection;
  38 import java.util.Collections;
  39 import java.util.List;
  40 import java.util.Locale;
  41 import java.util.Objects;
  42 import java.util.Set;
  43 
  44 import static javax.tools.DocumentationTool.Location.*;
  45 
  46 import javax.tools.JavaFileManager;
  47 import javax.tools.JavaFileObject;
  48 import javax.tools.StandardJavaFileManager;
  49 import javax.tools.StandardLocation;
  50 
  51 import com.sun.tools.javac.api.JavacTrees;
  52 import com.sun.tools.javac.file.BaseFileManager;
  53 import com.sun.tools.javac.file.JavacFileManager;
  54 import com.sun.tools.javac.main.CommandLine;
  55 import com.sun.tools.javac.main.OptionHelper;
  56 import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
  57 import com.sun.tools.javac.platform.PlatformDescription;
  58 import com.sun.tools.javac.platform.PlatformUtils;
  59 import com.sun.tools.javac.util.ClientCodeException;
  60 import com.sun.tools.javac.util.Context;
  61 import com.sun.tools.javac.util.Log;
  62 import com.sun.tools.javac.util.Options;
  63 
  64 import jdk.javadoc.doclet.Doclet;
  65 import jdk.javadoc.doclet.Doclet.Option;
  66 import jdk.javadoc.doclet.DocletEnvironment;
  67 
  68 import static com.sun.tools.javac.main.Option.*;
  69 
  70 /**
  71  * Main program of Javadoc.
  72  * Previously named "Main".
  73  *
  74  *  <p><b>This is NOT part of any supported API.
  75  *  If you write code that depends on this, you do so at your own risk.
  76  *  This code and its internal interfaces are subject to change or
  77  *  deletion without notice.</b>
  78  *
  79  * @author Robert Field
  80  * @author Neal Gafter (rewrite)
  81  */
  82 public class Start extends ToolOption.Helper {
  83 
  84     @SuppressWarnings("deprecation")
  85     private static final Class<?> OldStdDoclet =
  86             com.sun.tools.doclets.standard.Standard.class;
  87 
  88     private static final Class<?> StdDoclet =
  89             jdk.javadoc.internal.doclets.standard.Standard.class;
  90     /** Context for this invocation. */
  91     private final Context context;
  92 
  93     private static final String ProgramName = "javadoc";
  94 
  95     // meaning we allow all visibility of PROTECTED and PUBLIC
  96     private static final String defaultModifier = "protected";
  97 
  98     private Messager messager;
  99 
 100     private final String docletName;
 101 
 102     private final ClassLoader classLoader;
 103 
 104     private Class<?> docletClass;
 105 
 106     private Doclet doclet;
 107 
 108     // used to determine the locale for the messager
 109     private Locale locale;
 110 
 111 
 112     /**
 113      * In API mode, exceptions thrown while calling the doclet are
 114      * propagated using ClientCodeException.
 115      */
 116     private boolean apiMode;
 117 
 118     private JavaFileManager fileManager;
 119 
 120     Start() {
 121         this(null, null, null, null, null);
 122     }
 123 
 124     Start(PrintWriter writer) {
 125         this(null, null, writer, null, null);
 126     }
 127 
 128     Start(Context context, String programName, PrintWriter writer,
 129             String docletName, ClassLoader classLoader) {
 130         this.context = context == null ? new Context() : context;
 131         String pname = programName == null ? ProgramName : programName;
 132         this.messager = writer == null
 133                 ? new Messager(this.context, pname)
 134                 : new Messager(this.context, pname, writer, writer);
 135         this.docletName = docletName;
 136         this.classLoader = classLoader;
 137         this.docletClass = null;
 138         this.locale = Locale.getDefault();
 139     }
 140 
 141     public Start(Context context) {
 142         this.docletClass = null;
 143         this.context = Objects.requireNonNull(context);
 144         this.apiMode = true;
 145         this.docletName = null;
 146         this.classLoader = null;
 147         this.locale = Locale.getDefault();
 148     }
 149 
 150     void initMessager() {
 151         if (!apiMode)
 152             return;
 153         if (messager == null) {
 154             Log log = context.get(Log.logKey);
 155             if (log instanceof Messager) {
 156                 messager = (Messager) log;
 157             } else {
 158                 PrintWriter out = context.get(Log.outKey);
 159                 messager = (out == null)
 160                         ? new Messager(context, ProgramName)
 161                         : new Messager(context, ProgramName, out, out);
 162             }
 163         }
 164     }
 165 
 166     /**
 167      * Usage
 168      */
 169     @Override
 170     void usage() {
 171         usage(true);
 172     }
 173 
 174     void usage(boolean exit) {
 175         usage("main.usage", "-help", null, exit);
 176     }
 177 
 178     @Override
 179     void Xusage() {
 180         Xusage(true);
 181     }
 182 
 183     void Xusage(boolean exit) {
 184         usage("main.Xusage", "-X", "main.Xusage.foot", exit);
 185     }
 186 
 187     private void usage(String main, String option, String foot, boolean exit) {
 188         messager.notice(main);
 189         // let doclet print usage information (does nothing on error)
 190         if (docletClass != null) {
 191             String name = doclet.getName();
 192             Set<Option> supportedOptions = doclet.getSupportedOptions();
 193             messager.notice("main.doclet.usage.header", name);
 194             Option.Kind myKind = option.equals("-X")
 195                     ? Option.Kind.EXTENDED
 196                     : Option.Kind.STANDARD;
 197             supportedOptions.stream()
 198                     .filter(opt -> opt.getKind() == myKind)
 199                     .forEach(opt -> messager.printNotice(opt.toString()));
 200         }
 201         if (foot != null)
 202             messager.notice(foot);
 203 
 204         if (exit)
 205             throw new Messager.ExitJavadoc();
 206     }
 207 
 208 
 209     /**
 210      * Main program - external wrapper. In order to maintain backward
 211      * CLI  compatibility, we dispatch to the old tool or the old doclet's
 212      * Start mechanism, based on the options present on the command line
 213      * with the following precedence:
 214      *   1. presence of -Xold, dispatch to old tool
 215      *   2. doclet variant, if old, dispatch to old Start
 216      *   3. taglet variant, if old, dispatch to old Start
 217      *
 218      * Thus the presence of -Xold switches the tool, soon after command files
 219      * if any, are expanded, this is performed here, noting that the messager
 220      * is available at this point in time.
 221      * The doclet/taglet tests are performed in the begin method, further on,
 222      * this is to minimize argument processing and most importantly the impact
 223      * of class loader creation, needed to detect the doclet/taglet class variants.
 224      */
 225     @SuppressWarnings("deprecation")
 226     int begin(String... argv) {
 227         // Preprocess @file arguments
 228         try {
 229             argv = CommandLine.parse(argv);
 230         } catch (FileNotFoundException e) {
 231             messager.error("main.cant.read", e.getMessage());
 232             throw new Messager.ExitJavadoc();
 233         } catch (IOException e) {
 234             e.printStackTrace(System.err);
 235             throw new Messager.ExitJavadoc();
 236         }
 237 
 238         if (argv.length > 0 && "-Xold".equals(argv[0])) {
 239             messager.warning("main.legacy_api");
 240             String[] nargv = Arrays.copyOfRange(argv, 1, argv.length);
 241             return com.sun.tools.javadoc.Main.execute(nargv);
 242         }
 243         boolean ok = begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet());
 244         return ok ? 0 : 1;
 245     }
 246 
 247     // Called by 199 API.
 248     public boolean begin(Class<?> docletClass,
 249             Iterable<String> options,
 250             Iterable<? extends JavaFileObject> fileObjects) {
 251         this.docletClass = docletClass;
 252         List<String> opts = new ArrayList<>();
 253         for (String opt: options)
 254             opts.add(opt);
 255 
 256         return begin(opts, fileObjects);
 257     }
 258 
 259     @SuppressWarnings("deprecation")
 260     private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
 261         fileManager = context.get(JavaFileManager.class);
 262         if (fileManager == null) {
 263             JavacFileManager.preRegister(context);
 264             fileManager = context.get(JavaFileManager.class);
 265             if (fileManager instanceof BaseFileManager) {
 266                 ((BaseFileManager) fileManager).autoClose = true;
 267             }
 268         }
 269         // locale, doclet and maybe taglet, needs to be determined first
 270         docletClass = preProcess(fileManager, options);
 271         if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
 272             // no need to dispatch to old, safe to init now
 273             initMessager();
 274             messager.setLocale(locale);
 275             try {
 276                 Object o = docletClass.getConstructor(new Class<?>[0]).newInstance((Object[])null);
 277                 doclet = (Doclet) o;
 278             } catch (ReflectiveOperationException exc) {
 279                 exc.printStackTrace();
 280                 if (!apiMode) {
 281                     error("main.could_not_instantiate_class", docletClass);
 282                     throw new Messager.ExitJavadoc();
 283                 }
 284                 throw new ClientCodeException(exc);
 285             }
 286         } else {
 287             if (this.apiMode) {
 288                 com.sun.tools.javadoc.main.Start ostart
 289                         = new com.sun.tools.javadoc.main.Start(context);
 290                 return ostart.begin(docletClass, options, fileObjects);
 291             }
 292             warn("main.legacy_api");
 293             String[] array = options.toArray(new String[options.size()]);
 294             return com.sun.tools.javadoc.Main.execute(array) == 0;
 295         }
 296 
 297         boolean failed = false;
 298         try {
 299             failed = !parseAndExecute(options, fileObjects);
 300         } catch (Messager.ExitJavadoc exc) {
 301             // ignore, we just exit this way
 302         } catch (OutOfMemoryError ee) {
 303             messager.error("main.out.of.memory");
 304             failed = true;
 305         } catch (ClientCodeException e) {
 306             // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
 307             throw e;
 308         } catch (Error ee) {
 309             ee.printStackTrace(System.err);
 310             messager.error("main.fatal.error");
 311             failed = true;
 312         } catch (Exception ee) {
 313             ee.printStackTrace(System.err);
 314             messager.error("main.fatal.exception");
 315             failed = true;
 316         } finally {
 317             if (fileManager != null
 318                     && fileManager instanceof BaseFileManager
 319                     && ((BaseFileManager) fileManager).autoClose) {
 320                 try {
 321                     fileManager.close();
 322                 } catch (IOException ignore) {}
 323             }
 324             boolean haveErrorWarnings = messager.nerrors() > 0 ||
 325                     (rejectWarnings && messager.nwarnings() > 0);
 326             if (failed && !haveErrorWarnings) {
 327                 // the doclet failed, but nothing reported, flag it!.
 328                 messager.error("main.unknown.error");
 329             }
 330             failed |= haveErrorWarnings;
 331             messager.exitNotice();
 332             messager.flush();
 333         }
 334         return !failed;
 335     }
 336 
 337     /**
 338      * Main program - internal
 339      */
 340     private boolean parseAndExecute(List<String> argList,
 341             Iterable<? extends JavaFileObject> fileObjects) throws IOException {
 342         long tm = System.currentTimeMillis();
 343 
 344         List<String> javaNames = new ArrayList<>();
 345 
 346         compOpts = Options.instance(context);
 347 
 348         // Make sure no obsolete source/target messages are reported
 349         com.sun.tools.javac.main.Option.XLINT.process(getOptionHelper(), "-Xlint:-options");
 350 
 351         doclet.init(locale, messager);
 352         parseArgs(argList, javaNames);
 353 
 354         if (fileManager instanceof BaseFileManager) {
 355             ((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
 356         }
 357 
 358         String platformString = compOpts.get("-release");
 359 
 360         if (platformString != null) {
 361             if (compOpts.isSet("-source")) {
 362                 usageError("main.release.bootclasspath.conflict", "-source");
 363             }
 364             if (fileManagerOpts.containsKey(BOOTCLASSPATH)) {
 365                 usageError("main.release.bootclasspath.conflict", BOOTCLASSPATH.getText());
 366             }
 367 
 368             PlatformDescription platformDescription =
 369                     PlatformUtils.lookupPlatformDescription(platformString);
 370 
 371             if (platformDescription == null) {
 372                 usageError("main.unsupported.release.version", platformString);
 373             }
 374 
 375             compOpts.put(SOURCE, platformDescription.getSourceVersion());
 376 
 377             context.put(PlatformDescription.class, platformDescription);
 378 
 379             Collection<Path> platformCP = platformDescription.getPlatformPath();
 380 
 381             if (platformCP != null) {
 382                 if (fileManager instanceof StandardJavaFileManager) {
 383                     StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
 384 
 385                     sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
 386                 } else {
 387                     usageError("main.release.not.standard.file.manager", platformString);
 388                 }
 389             }
 390         }
 391 
 392         compOpts.notifyListeners();
 393 
 394         if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) {
 395             usageError("main.No_packages_or_classes_specified");
 396         }
 397 
 398         JavadocTool comp = JavadocTool.make0(context);
 399         if (comp == null) return false;
 400 
 401         if (showAccess == null) {
 402             setFilter(defaultModifier);
 403         }
 404 
 405         DocletEnvironment root = comp.getEnvironment(
 406                 encoding,
 407                 showAccess,
 408                 overviewpath,
 409                 javaNames,
 410                 fileObjects,
 411                 subPackages,
 412                 excludedPackages,
 413                 docClasses,
 414                 quiet);
 415 
 416         // release resources
 417         comp = null;
 418 
 419         if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
 420             JavacTrees trees = JavacTrees.instance(context);
 421             trees.setBreakIterator(BreakIterator.getSentenceInstance(locale));
 422         }
 423         // pass off control to the doclet
 424         boolean ok = root != null;
 425         if (ok) ok = doclet.run(root);
 426 
 427         // We're done.
 428         if (compOpts.get("-verbose") != null) {
 429             tm = System.currentTimeMillis() - tm;
 430             messager.notice("main.done_in", Long.toString(tm));
 431         }
 432 
 433         return ok;
 434     }
 435 
 436     Set<Doclet.Option> docletOptions = null;
 437     int handleDocletOptions(int idx, List<String> args, boolean isToolOption) {
 438         if (docletOptions == null) {
 439             docletOptions = doclet.getSupportedOptions();
 440         }
 441         String arg = args.get(idx);
 442 
 443         for (Doclet.Option opt : docletOptions) {
 444             if (opt.matches(arg)) {
 445                 if (args.size() - idx < opt.getArgumentCount()) {
 446                     usageError("main.requires_argument", arg);
 447                 }
 448                 opt.process(arg, args.listIterator(idx + 1));
 449                 idx += opt.getArgumentCount();
 450                 return idx;
 451             }
 452         }
 453         // check if arg is accepted by the tool before emitting error
 454         if (!isToolOption)
 455             usageError("main.invalid_flag", arg);
 456         return idx;
 457     }
 458 
 459     private Class<?> preProcess(JavaFileManager jfm, List<String> argv) {
 460         // doclet specifying arguments
 461         String userDocletPath = null;
 462         String userDocletName = null;
 463 
 464         // taglet specifying arguments, since tagletpath is a doclet
 465         // functionality, assume they are repeated and inspect all.
 466         List<File> userTagletPath = new ArrayList<>();
 467         List<String> userTagletNames = new ArrayList<>();
 468 
 469         // Step 1: loop through the args, set locale early on, if found.
 470         for (int i = 0 ; i < argv.size() ; i++) {
 471             String arg = argv.get(i);
 472             if (arg.equals(ToolOption.LOCALE.opt)) {
 473                 oneArg(argv, i++);
 474                 String lname = argv.get(i);
 475                 locale = getLocale(lname);
 476             } else if (arg.equals(ToolOption.DOCLET.opt)) {
 477                 oneArg(argv, i++);
 478                 if (userDocletName != null) {
 479                     usageError("main.more_than_one_doclet_specified_0_and_1",
 480                             userDocletName, argv.get(i));
 481                 }
 482                 if (docletName != null) {
 483                     usageError("main.more_than_one_doclet_specified_0_and_1",
 484                             docletName, argv.get(i));
 485                 }
 486                 userDocletName = argv.get(i);
 487             } else if (arg.equals(ToolOption.DOCLETPATH.opt)) {
 488                 oneArg(argv, i++);
 489                 if (userDocletPath == null) {
 490                     userDocletPath = argv.get(i);
 491                 } else {
 492                     userDocletPath += File.pathSeparator + argv.get(i);
 493                 }
 494             } else if ("-taglet".equals(arg)) {
 495                 userTagletNames.add(argv.get(i + 1));
 496             } else if ("-tagletpath".equals(arg)) {
 497                 for (String pathname : argv.get(i + 1).split(File.pathSeparator)) {
 498                     userTagletPath.add(new File(pathname));
 499                 }
 500             }
 501         }
 502 
 503         // Step 2: a doclet is provided, nothing more to do.
 504         if (docletClass != null) {
 505             return docletClass;
 506         }
 507 
 508         // Step 3: doclet name specified ? if so find a ClassLoader,
 509         // and load it.
 510         if (userDocletName != null) {
 511             ClassLoader cl = classLoader;
 512             if (cl == null) {
 513                 if (!fileManager.hasLocation(DOCLET_PATH)) {
 514                     List<File> paths = new ArrayList<>();
 515                     if (userDocletPath != null) {
 516                         for (String pathname : userDocletPath.split(File.pathSeparator)) {
 517                             paths.add(new File(pathname));
 518                         }
 519                     }
 520                     try {
 521                         ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
 522                     } catch (IOException ioe) {
 523                         error("main.doclet_could_not_set_location", paths);
 524                         throw new Messager.ExitJavadoc();
 525                     }
 526                 }
 527                 cl = fileManager.getClassLoader(DOCLET_PATH);
 528                 if (cl == null) {
 529                     // despite doclet specified on cmdline no classloader found!
 530                     error("main.doclet_no_classloader_found", userDocletName);
 531                     throw new Messager.ExitJavadoc();
 532                 }
 533             }
 534             try {
 535                 Class<?> klass = cl.loadClass(userDocletName);
 536                 return klass;
 537             } catch (ClassNotFoundException cnfe) {
 538                 error("main.doclet_class_not_found", userDocletName);
 539                 throw new Messager.ExitJavadoc();
 540             }
 541         }
 542 
 543         // Step 4: we have a doclet, try loading it
 544         if (docletName != null) {
 545             try {
 546                 return Class.forName(docletName, true, getClass().getClassLoader());
 547             } catch (ClassNotFoundException cnfe) {
 548                 error("main.doclet_class_not_found", userDocletName);
 549                 throw new Messager.ExitJavadoc();
 550             }
 551         }
 552 
 553         // Step 5: we don't have a doclet specified, do we have taglets ?
 554         if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) {
 555             // found a bogey, return the old doclet
 556             return OldStdDoclet;
 557         }
 558 
 559         // finally
 560         return StdDoclet;
 561     }
 562 
 563     /*
 564      * This method returns true iff it finds a legacy taglet, but for
 565      * all other conditions including errors it returns false, allowing
 566      * nature to take its own course.
 567      */
 568     @SuppressWarnings("deprecation")
 569     private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) {
 570         if (!fileManager.hasLocation(TAGLET_PATH)) {
 571             try {
 572                 ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths);
 573             } catch (IOException ioe) {
 574                 error("main.doclet_could_not_set_location", tagletPaths);
 575                 throw new Messager.ExitJavadoc();
 576             }
 577         }
 578         ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH);
 579         if (cl == null) {
 580             // no classloader found!
 581             error("main.doclet_no_classloader_found", tagletNames.get(0));
 582             throw new Messager.ExitJavadoc();
 583         }
 584         for (String tagletName : tagletNames) {
 585             try {
 586                 Class<?> klass = cl.loadClass(tagletName);
 587                 if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) {
 588                     return true;
 589                 }
 590             } catch (ClassNotFoundException cnfe) {
 591                 error("main.doclet_class_not_found", tagletName);
 592                 throw new Messager.ExitJavadoc();
 593             }
 594         }
 595         return false;
 596     }
 597 
 598     private void parseArgs(List<String> args, List<String> javaNames) {
 599         for (int i = 0 ; i < args.size() ; i++) {
 600             String arg = args.get(i);
 601             ToolOption o = ToolOption.get(arg);
 602             if (o != null) {
 603                 // handle a doclet argument that may be needed however
 604                 // don't increment the index, and allow the tool to consume args
 605                 handleDocletOptions(i, args, true);
 606 
 607                 if (o.hasArg) {
 608                     oneArg(args, i++);
 609                     o.process(this, args.get(i));
 610                 } else if (o.hasSuffix) {
 611                     o.process(this, arg);
 612                 } else {
 613                     setOption(arg);
 614                     o.process(this);
 615                 }
 616             } else if (arg.startsWith("-XD")) {
 617                 // hidden javac options
 618                 String s = arg.substring("-XD".length());
 619                 int eq = s.indexOf('=');
 620                 String key = (eq < 0) ? s : s.substring(0, eq);
 621                 String value = (eq < 0) ? s : s.substring(eq+1);
 622                 compOpts.put(key, value);
 623             } else if (arg.startsWith("-")) {
 624                 i = handleDocletOptions(i, args, false);
 625             } else {
 626                 javaNames.add(arg);
 627             }
 628         }
 629     }
 630 
 631     private <T> boolean isEmpty(Iterable<T> iter) {
 632         return !iter.iterator().hasNext();
 633     }
 634 
 635     /**
 636      * Set one arg option.
 637      * Error and exit if one argument is not provided.
 638      */
 639     private void oneArg(List<String> args, int index) {
 640         if ((index + 1) < args.size()) {
 641             setOption(args.get(index), args.get(index+1));
 642         } else {
 643             usageError("main.requires_argument", args.get(index));
 644         }
 645     }
 646 
 647     @Override
 648     void usageError(String key, Object... args) {
 649         error(key, args);
 650         usage(true);
 651     }
 652 
 653     void error(String key, Object... args) {
 654         messager.error(key, args);
 655     }
 656 
 657     void warn(String key, Object... args)  {
 658         messager.warning(key, args);
 659     }
 660 
 661     /**
 662      * indicate an option with no arguments was given.
 663      */
 664     private void setOption(String opt) {
 665         String[] option = { opt };
 666         options.add(Arrays.asList(option));
 667     }
 668 
 669     /**
 670      * indicate an option with one argument was given.
 671      */
 672     private void setOption(String opt, String argument) {
 673         String[] option = { opt, argument };
 674         options.add(Arrays.asList(option));
 675     }
 676 
 677     /**
 678      * indicate an option with the specified list of arguments was given.
 679      */
 680     private void setOption(String opt, List<String> arguments) {
 681         List<String> args = new ArrayList<>(arguments.size() + 1);
 682         args.add(opt);
 683         args.addAll(arguments);
 684         options.add(args);
 685     }
 686 
 687     /**
 688      * Get the locale if specified on the command line
 689      * else return null and if locale option is not used
 690      * then return default locale.
 691      */
 692     private Locale getLocale(String localeName) {
 693         Locale userlocale = null;
 694         if (localeName == null || localeName.isEmpty()) {
 695             return Locale.getDefault();
 696         }
 697         int firstuscore = localeName.indexOf('_');
 698         int seconduscore = -1;
 699         String language = null;
 700         String country = null;
 701         String variant = null;
 702         if (firstuscore == 2) {
 703             language = localeName.substring(0, firstuscore);
 704             seconduscore = localeName.indexOf('_', firstuscore + 1);
 705             if (seconduscore > 0) {
 706                 if (seconduscore != firstuscore + 3
 707                         || localeName.length() <= seconduscore + 1) {
 708                     usageError("main.malformed_locale_name", localeName);
 709                     return null;
 710                 }
 711                 country = localeName.substring(firstuscore + 1,
 712                         seconduscore);
 713                 variant = localeName.substring(seconduscore + 1);
 714             } else if (localeName.length() == firstuscore + 3) {
 715                 country = localeName.substring(firstuscore + 1);
 716             } else {
 717                 usageError("main.malformed_locale_name", localeName);
 718                 return null;
 719             }
 720         } else if (firstuscore == -1 && localeName.length() == 2) {
 721             language = localeName;
 722         } else {
 723             usageError("main.malformed_locale_name", localeName);
 724             return null;
 725         }
 726         userlocale = searchLocale(language, country, variant);
 727         if (userlocale == null) {
 728             usageError("main.illegal_locale_name", localeName);
 729             return null;
 730         } else {
 731             return userlocale;
 732         }
 733     }
 734 
 735     /**
 736      * Search the locale for specified language, specified country and
 737      * specified variant.
 738      */
 739     private Locale searchLocale(String language, String country,
 740                                 String variant) {
 741         for (Locale loc : Locale.getAvailableLocales()) {
 742             if (loc.getLanguage().equals(language) &&
 743                 (country == null || loc.getCountry().equals(country)) &&
 744                 (variant == null || loc.getVariant().equals(variant))) {
 745                 return loc;
 746             }
 747         }
 748         return null;
 749     }
 750 
 751     @Override
 752     OptionHelper getOptionHelper() {
 753         return new GrumpyHelper(null) {
 754             @Override
 755             public String get(com.sun.tools.javac.main.Option option) {
 756                 return compOpts.get(option);
 757             }
 758 
 759             @Override
 760             public void put(String name, String value) {
 761                 compOpts.put(name, value);
 762             }
 763         };
 764     }
 765 }