1 /*
   2  * Copyright (c) 2014, 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.sjavac.options;
  27 
  28 import java.nio.file.Path;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.Collection;
  32 import java.util.HashMap;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.Set;
  36 import java.util.HashSet;
  37 import java.util.StringJoiner;
  38 
  39 import com.sun.tools.sjavac.Transformer;
  40 import com.sun.tools.sjavac.Util;
  41 
  42 /**
  43  * Instances of this class represent values for sjavac command line options.
  44  *
  45  *  <p><b>This is NOT part of any supported API.
  46  *  If you write code that depends on this, you do so at your own risk.
  47  *  This code and its internal interfaces are subject to change or
  48  *  deletion without notice.</b>
  49  */
  50 public class Options {
  51 
  52     // Output directories
  53     private Path destDir, genSrcDir, headerDir, stateDir;
  54 
  55     // Input directories
  56     private List<SourceLocation> sources = new ArrayList<>();
  57     private List<SourceLocation> sourceSearchPaths = new ArrayList<>();
  58     private List<SourceLocation> classSearchPaths = new ArrayList<>();
  59     private List<SourceLocation> moduleSearchPaths = new ArrayList<>();
  60 
  61     private String logLevel = "info";
  62 
  63     private Set<String> permitted_artifacts = new HashSet<>();
  64     private boolean permitUnidentifiedArtifacts = false;
  65     private boolean permitSourcesInDefaultPackage = false;
  66 
  67     private Path sourceReferenceList;
  68     private int numCores = 4;
  69     private String implicitPolicy = "none";
  70     private List<String> javacArgs = new ArrayList<>();
  71 
  72     private Map<String, Transformer> trRules = new HashMap<>();
  73 
  74     private boolean startServer = false;
  75 
  76     // Server configuration string
  77     private String serverConf;
  78 
  79     /** Get the policy for implicit classes */
  80     public String getImplicitPolicy() {
  81         return implicitPolicy;
  82     }
  83 
  84     /** Get the path for generated sources (or null if no such path is set) */
  85     public Path getGenSrcDir() {
  86         return genSrcDir;
  87     }
  88 
  89     /** Get the path for the destination directory */
  90     public Path getDestDir() {
  91         return destDir;
  92     }
  93 
  94     /** Get the path for the header directory (or null if no such path is set) */
  95     public Path getHeaderDir() {
  96         return headerDir;
  97     }
  98 
  99     /** Get the path for the state directory, defaults to destDir. */
 100     public Path getStateDir() {
 101         return stateDir;
 102     }
 103 
 104     /** Get all source locations for files to be compiled */
 105     public List<SourceLocation> getSources() {
 106         return sources;
 107     }
 108 
 109     /**
 110      * Get all paths to search for classes in .java format. (Java-files in
 111      * found here should not be compiled.
 112      */
 113     public List<SourceLocation> getSourceSearchPaths() {
 114         return sourceSearchPaths;
 115     }
 116 
 117     /** Get all paths to search for classes in. */
 118     public List<SourceLocation> getClassSearchPath() {
 119         return classSearchPaths;
 120     }
 121 
 122     /** Get all paths to search for modules in. */
 123     public List<SourceLocation> getModuleSearchPaths() {
 124         return moduleSearchPaths;
 125     }
 126 
 127     /** Get the log level. */
 128     public String getLogLevel() {
 129         return logLevel;
 130     }
 131 
 132     /** Returns true iff the artifact is permitted in the output dir. */
 133     public boolean isUnidentifiedArtifactPermitted(String f) {
 134         return permitted_artifacts.contains(f);
 135     }
 136 
 137     /** Returns true iff artifacts in the output directories should be kept,
 138      * even if they would not be generated in a clean build. */
 139     public boolean areUnidentifiedArtifactsPermitted() {
 140         return permitUnidentifiedArtifacts;
 141     }
 142 
 143     /** Returns true iff sources in the default package should be permitted. */
 144     public boolean isDefaultPackagePermitted() {
 145         return permitSourcesInDefaultPackage;
 146     }
 147 
 148     /** Get the path to the list of reference sources (or null if none is set) */
 149     public Path getSourceReferenceList() {
 150         return sourceReferenceList;
 151     }
 152 
 153     /** Get the number of cores to be used by sjavac */
 154     public int getNumCores() {
 155         return numCores;
 156     }
 157 
 158     /** Returns all arguments relevant to javac but irrelevant to sjavac. */
 159     public List<String> getJavacArgs() {
 160         return javacArgs;
 161     }
 162 
 163     /**
 164      * Get a map which maps suffixes to transformers (for example
 165      * ".java" {@literal ->} CompileJavaPackages)
 166      */
 167     public Map<String, Transformer> getTranslationRules() {
 168         return trRules;
 169     }
 170 
 171     /** Return true iff a new server should be started */
 172     public boolean startServerFlag() {
 173         return startServer;
 174     }
 175 
 176     /** Return the server configuration string. */
 177     public String getServerConf() {
 178         return serverConf;
 179     }
 180 
 181     /**
 182      * Parses the given argument array and returns a corresponding Options
 183      * instance.
 184      */
 185     public static Options parseArgs(String... args) {
 186         Options options = new Options();
 187         options.new ArgDecoderOptionHelper().traverse(args);
 188         return options;
 189     }
 190 
 191     /** Returns true iff a .java file is among the javac arguments */
 192     public boolean isJavaFilesAmongJavacArgs() {
 193         for (String javacArg : javacArgs)
 194             if (javacArg.endsWith(".java"))
 195                 return true;
 196         return false;
 197     }
 198 
 199     /**
 200      * Returns a string representation of the options that affect the result of
 201      * the compilation. (Used for saving the state of the options used in a
 202      * previous compile.)
 203      */
 204     public String getStateArgsString() {
 205 
 206         // Local utility class for collecting the arguments
 207         class StateArgs {
 208 
 209             private List<String> args = new ArrayList<>();
 210 
 211             void addArg(Option opt) {
 212                 args.add(opt.arg);
 213             }
 214 
 215             void addArg(Option opt, Object val) {
 216                 addArg(opt);
 217                 args.add(val.toString());
 218             }
 219 
 220             void addSourceLocations(Option opt, List<SourceLocation> locs) {
 221                 for (SourceLocation sl : locs) {
 222                     for (String pkg : sl.includes) addArg(Option.I, pkg);
 223                     for (String pkg : sl.excludes) addArg(Option.X, pkg);
 224                     addArg(opt, sl.getPath());
 225                 }
 226             }
 227 
 228             String getResult() {
 229                 return String.join(" ", args);
 230             }
 231 
 232             public void addAll(Collection<String> toAdd) {
 233                 args.addAll(toAdd);
 234             }
 235         }
 236 
 237         StateArgs args = new StateArgs();
 238 
 239         // Directories
 240         if (genSrcDir != null)
 241             args.addArg(Option.S, genSrcDir.normalize());
 242 
 243         if (headerDir != null)
 244             args.addArg(Option.H, headerDir.normalize());
 245 
 246         if (destDir != null)
 247             args.addArg(Option.D, destDir.normalize());
 248 
 249         if (stateDir != null)
 250             args.addArg(Option.STATE_DIR, stateDir.normalize());
 251 
 252         // Source roots
 253         args.addSourceLocations(Option.SRC, sources);
 254         args.addSourceLocations(Option.SOURCEPATH, sourceSearchPaths);
 255         args.addSourceLocations(Option.CLASSPATH,  classSearchPaths);
 256         args.addSourceLocations(Option.MODULEPATH, moduleSearchPaths);
 257 
 258         // Boolean options
 259         if (permitSourcesInDefaultPackage)
 260             args.addArg(Option.PERMIT_SOURCES_WITHOUT_PACKAGE);
 261 
 262         for (String f : permitted_artifacts) {
 263             args.addArg(Option.PERMIT_ARTIFACT, f);
 264         }
 265 
 266         if (permitUnidentifiedArtifacts)
 267             args.addArg(Option.PERMIT_UNIDENTIFIED_ARTIFACTS);
 268 
 269         // Translation rules
 270         for (Map.Entry<String, Transformer> tr : trRules.entrySet()) {
 271             String val = tr.getKey() + "=" + tr.getValue().getClass().getName();
 272             args.addArg(Option.TR, val);
 273         }
 274 
 275         // Javac args
 276         args.addAll(javacArgs);
 277 
 278         return args.getResult();
 279     }
 280 
 281 
 282     /** Extract the arguments to be passed on to javac. */
 283     public String[] prepJavacArgs() {
 284         List<String> args = new ArrayList<>();
 285 
 286         // Output directories
 287         args.add("-d");
 288         args.add(destDir.toString());
 289 
 290         if (getGenSrcDir() != null) {
 291             args.add("-s");
 292             args.add(genSrcDir.toString());
 293         }
 294 
 295         if (headerDir != null) {
 296             args.add("-h");
 297             args.add(headerDir.toString());
 298         }
 299 
 300         // Prep sourcepath
 301         List<SourceLocation> sourcepath = new ArrayList<>();
 302         sourcepath.addAll(sources);
 303         sourcepath.addAll(sourceSearchPaths);
 304         if (sourcepath.size() > 0) {
 305             args.add("-sourcepath");
 306             args.add(concatenateSourceLocations(sourcepath));
 307         }
 308 
 309         // Prep classpath
 310         if (classSearchPaths.size() > 0) {
 311             args.add("-classpath");
 312             args.add(concatenateSourceLocations(classSearchPaths));
 313         }
 314 
 315         // Enable dependency generation
 316         args.add("-Xdebug:completionDeps=source,class");
 317 
 318         // This can't be anything but 'none'. Enforced by sjavac main method.
 319         args.add("-implicit:" + implicitPolicy);
 320 
 321         // If this option is not used, Object for instance is erroneously
 322         // picked up from PLATFORM_CLASS_PATH instead of CLASS_PATH.
 323         //
 324         // Discussing this further led to the decision of letting bootclasspath
 325         // be a dummy (empty) directory when building the JDK.
 326         //args.add("-XXuserPathsFirst");
 327 
 328         // Append javac-options (i.e. pass through options not recognized by
 329         // sjavac to javac.)
 330         args.addAll(javacArgs);
 331 
 332         return args.toArray(new String[args.size()]);
 333     }
 334 
 335     // Helper method to join a list of source locations separated by
 336     // File.pathSeparator
 337     private static String concatenateSourceLocations(List<SourceLocation> locs) {
 338         StringJoiner joiner = new StringJoiner(java.io.File.pathSeparator);
 339         for (SourceLocation loc : locs) {
 340             joiner.add(loc.getPath().toString());
 341         }
 342         return joiner.toString();
 343     }
 344 
 345     // OptionHelper that records the traversed options in this Options instance.
 346     private class ArgDecoderOptionHelper extends OptionHelper {
 347 
 348         List<String> includes, excludes, includeFiles, excludeFiles;
 349         {
 350             resetFilters();
 351         }
 352 
 353         boolean headerProvided = false;
 354         boolean genSrcProvided = false;
 355         boolean stateProvided = false;
 356 
 357         @Override
 358         public void reportError(String msg) {
 359             throw new IllegalArgumentException(msg);
 360         }
 361 
 362         @Override
 363         public void sourceRoots(List<Path> paths) {
 364             sources.addAll(createSourceLocations(paths));
 365         }
 366 
 367         @Override
 368         public void exclude(String exclPattern) {
 369             exclPattern = Util.normalizeDriveLetter(exclPattern);
 370             excludes.add(exclPattern);
 371         }
 372 
 373         @Override
 374         public void include(String inclPattern) {
 375             inclPattern = Util.normalizeDriveLetter(inclPattern);
 376             includes.add(inclPattern);
 377         }
 378 
 379         @Override
 380         public void addTransformer(String suffix, Transformer tr) {
 381             if (trRules.containsKey(suffix)) {
 382                 reportError("More than one transformer specified for " +
 383                             "suffix " + suffix + ".");
 384                 return;
 385             }
 386             trRules.put(suffix, tr);
 387         }
 388 
 389         @Override
 390         public void sourcepath(List<Path> paths) {
 391             sourceSearchPaths.addAll(createSourceLocations(paths));
 392         }
 393 
 394         @Override
 395         public void modulepath(List<Path> paths) {
 396             moduleSearchPaths.addAll(createSourceLocations(paths));
 397         }
 398 
 399         @Override
 400         public void classpath(List<Path> paths) {
 401             classSearchPaths.addAll(createSourceLocations(paths));
 402         }
 403 
 404         @Override
 405         public void numCores(int n) {
 406             numCores = n;
 407         }
 408 
 409         @Override
 410         public void logLevel(String level) {
 411             logLevel = level;
 412         }
 413 
 414         @Override
 415         public void compareFoundSources(Path referenceList) {
 416             sourceReferenceList = referenceList;
 417         }
 418 
 419         @Override
 420         public void permitArtifact(String f) {
 421             permitted_artifacts.add(f);
 422         }
 423 
 424         @Override
 425         public void permitUnidentifiedArtifacts() {
 426             permitUnidentifiedArtifacts = true;
 427         }
 428 
 429         @Override
 430         public void permitDefaultPackage() {
 431             permitSourcesInDefaultPackage = true;
 432         }
 433 
 434         @Override
 435         public void serverConf(String conf) {
 436             if (serverConf != null)
 437                 reportError("Can not specify more than one server configuration.");
 438             else
 439                 serverConf = conf;
 440         }
 441 
 442         @Override
 443         public void implicit(String policy) {
 444             implicitPolicy = policy;
 445         }
 446 
 447         @Override
 448         public void startServerConf(String conf) {
 449             if (serverConf != null)
 450                 reportError("Can not specify more than one server configuration.");
 451             else {
 452                 startServer = true;
 453                 serverConf = conf;
 454             }
 455         }
 456 
 457         @Override
 458         public void javacArg(String... arg) {
 459             javacArgs.addAll(Arrays.asList(arg));
 460         }
 461 
 462         @Override
 463         public void destDir(Path dir) {
 464             if (destDir != null) {
 465                 reportError("Destination directory already specified.");
 466                 return;
 467             }
 468             destDir = dir.toAbsolutePath();
 469         }
 470 
 471         @Override
 472         public void generatedSourcesDir(Path dir) {
 473             if (genSrcProvided) {
 474                 reportError("Directory for generated sources already specified.");
 475                 return;
 476             }
 477             genSrcProvided = true;
 478             genSrcDir = dir.toAbsolutePath();
 479         }
 480 
 481         @Override
 482         public void headerDir(Path dir) {
 483             if (headerProvided) {
 484                 reportError("Header directory already specified.");
 485                 return;
 486             }
 487             headerProvided = true;
 488             headerDir = dir.toAbsolutePath();
 489         }
 490 
 491         @Override
 492         public void stateDir(Path dir) {
 493             if (stateProvided) {
 494                 reportError("State directory already specified.");
 495                 return;
 496             }
 497             stateProvided = true;
 498             stateDir = dir.toAbsolutePath();
 499         }
 500 
 501         private List<SourceLocation> createSourceLocations(List<Path> paths) {
 502             List<SourceLocation> result = new ArrayList<>();
 503             for (Path path : paths) {
 504                 result.add(new SourceLocation(
 505                         path,
 506                         includes,
 507                         excludes));
 508             }
 509             resetFilters();
 510             return result;
 511         }
 512 
 513         private void resetFilters() {
 514             includes = new ArrayList<>();
 515             excludes = new ArrayList<>();
 516             includeFiles = new ArrayList<>();
 517             excludeFiles = new ArrayList<>();
 518         }
 519     }
 520 
 521 }