1 /*
   2  * Copyright (c) 2013, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.testlibrary.tasks;
  25 
  26 import java.nio.file.Path;
  27 import java.util.ArrayList;
  28 import java.util.Arrays;
  29 import static java.util.Arrays.asList;
  30 import static java.util.Arrays.stream;
  31 import static java.util.Collections.EMPTY_LIST;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.function.Function;
  35 import static java.util.stream.Collectors.joining;
  36 import static java.util.stream.Collectors.toList;
  37 import java.util.stream.Stream;
  38 
  39 /**
  40  * A task to configure and run the Java launcher.
  41  */
  42 public class JavaTask extends AbstractTask<JavaTask> {
  43     /**
  44      * {@code --class-path}
  45      */
  46     public static final String CLASS_PATH = "--class-path";
  47     /**
  48      * {@code --module-path}
  49      */
  50     public static final String MODULE_PATH = "--module-path";
  51     /**
  52      * {@code --upgrade-module-path}
  53      */
  54     public static final String UPGRADE_MODULE_PATH = "--upgrade-module-path";
  55     /**
  56      * {@code --add-modules}
  57      */
  58     public static final String ADD_MODULES = "--add-modules";
  59     /**
  60      * {@code --limit-modules}
  61      */
  62     public static final String LIMIT_MODULES = "--limit-modules";
  63     /**
  64      * {@code --add-exports}
  65      */
  66     public static final String ADD_EXPORTS = "--add-exports";
  67     /**
  68      * {@code --add-reads}
  69      */
  70     public static final String ADD_READS = "--add-reads";
  71     /**
  72      * {@code --patch-module}
  73      */
  74     public static final String PATCH_MODULE = "--patch-module";
  75 
  76     private List<String> classPath;
  77     private List<String> modulePath;
  78     private List<String> upgradeModulePath;
  79     private String mainClass;
  80     private String module;
  81     private List<String> vmOptions;
  82     private List<String> classArgs;
  83     private List<String> limitModules;
  84     private List<String> addModules;
  85     private List<String> standardOptions;
  86     private List<String> addExports = new ArrayList<>();
  87     private List<String> addReads = new ArrayList<>();
  88     private List<String> patchModule = new ArrayList<>();
  89 
  90     /**
  91      * Create a task to run the Java launcher, using {@code EXEC} mode.
  92      */
  93     public JavaTask() {
  94         super(Task.Mode.EXEC);
  95         standardOptions = Stream.of("test.vm.opts", "test.java.opts")
  96                 .map(p -> System.getProperty(p))
  97                 .filter(p -> p != null && !p.isEmpty())
  98                 .map(o -> asList(o.split(" +")))
  99                 .flatMap(l -> l.stream()).collect(toList());
 100     }
 101 
 102     /**
 103      * Sets the classpath.
 104      * @param classPath the classpath
 105      * @return this task object
 106      */
 107     public JavaTask classPath(String... classPath) {
 108         this.classPath = stream(classPath).collect(toList());
 109         return this;
 110     }
 111 
 112     /**
 113      * Sets the classpath.
 114      * @param classPath the classpath
 115      * @return this task object
 116      */
 117     public JavaTask classPath(Path... classPath) {
 118         this.classPath = stream(classPath)
 119                 .map(Object::toString).collect(toList());
 120         return this;
 121     }
 122 
 123     /**
 124      * Sets the module path.
 125      * @param modulePath the module path
 126      * @return this task object
 127      */
 128     public JavaTask modulePath(String... modulePath) {
 129         this.modulePath = stream(modulePath).collect(toList());
 130         return this;
 131     }
 132 
 133     /**
 134      * Sets the module path.
 135      * @param modulePath the module path
 136      * @return this task object
 137      */
 138     public JavaTask modulePath(Path... modulePath) {
 139         this.modulePath = stream(modulePath)
 140                 .map(Object::toString).collect(toList());
 141         return this;
 142     }
 143 
 144     /**
 145      * Sets the upgrade module path.
 146      * @param upgradeModulePath the upgrade module path
 147      * @return this task object
 148      */
 149     public JavaTask upgradeModulePath(String... upgradeModulePath) {
 150         this.upgradeModulePath = stream(upgradeModulePath).collect(toList());
 151         return this;
 152     }
 153 
 154     /**
 155      * Sets the upgrade module path.
 156      * @param upgradeModulePath the upgrade module path
 157      * @return this task object
 158      */
 159     public JavaTask upgradeModulePath(Path... upgradeModulePath) {
 160         this.upgradeModulePath = stream(upgradeModulePath)
 161                 .map(Object::toString).collect(toList());
 162         return this;
 163     }
 164 
 165     /**
 166      * Adds single occurrence of the {@code --add-exports} option.
 167      * @param source source module
 168      * @param pkg exported package
 169      * @param targets target modules. If empty, {@code ALL-UNNAMED} is used as 
 170      * the target.
 171      * @return this task object
 172      */
 173     public JavaTask addExports(String source, String pkg, String... targets) {
 174         String targetList;
 175         if(targets.length == 0) {
 176             targetList = ALL_UNNAMED;
 177         } else {
 178             targetList = stream(targets).collect(joining(","));
 179         }
 180         this.addExports.add(source + "/" + pkg + "=" + targetList);
 181         return this;
 182     }
 183 
 184     /**
 185      * Sets the {@code --add-modules} option.
 186      * @param addModules parameter for the option
 187      * @return this task object
 188      */
 189     public JavaTask addModules(String... addModules) {
 190         this.addModules = asList(addModules);
 191         return this;
 192     }
 193 
 194     /**
 195      * Adds single occurrence of the {@code --add-reads} option.
 196      * @param source source module
 197      * @param targets target modules. If empty, {@code ALL-UNNAMED} is used as 
 198      * the target.
 199      * @return this task object
 200      */
 201     public JavaTask addReads(String source, String... targets) {
 202         String targetList;
 203         if(targets.length == 0) {
 204             targetList = ALL_UNNAMED;
 205         } else {
 206             targetList = stream(targets).collect(joining(","));
 207         }
 208         this.addReads.add(source + "=" + targetList);
 209         return this;
 210     }
 211 
 212     /**
 213      * Adds single occurrence of the {@code --patch-module} option.
 214      * @param module module to be overridden or augmented
 215      * @param files jar files and directories
 216      * @return this task object
 217      */
 218     public JavaTask patchModule(String module, String... files) {
 219         patchModule(module, stream(files));
 220         return this;
 221     }
 222 
 223     /**
 224      * Adds single occurrence of the {@code --patch-module} option.
 225      * @param module module to be overridden or augmented
 226      * @param files jar files and directories
 227      * @return this task object
 228      */
 229     public JavaTask patchModule(String module, Path... files) {
 230         patchModule(module, stream(files).map(Object::toString));
 231         return this;
 232     }
 233 
 234     /**
 235      * Sets the {@code --limit-modules} option.
 236      * @param limitModules parameter for the option
 237      * @return this task object
 238      */
 239     public JavaTask limitModules(String... limitModules) {
 240         this.limitModules = asList(limitModules);
 241         return this;
 242     }
 243 
 244     /**
 245      * Sets the VM options.
 246      * @param vmOptions the options
 247      * @return this task object
 248      */
 249     public JavaTask vmOptions(String... vmOptions) {
 250         this.vmOptions = asList(vmOptions);
 251         return this;
 252     }
 253 
 254     /**
 255      * Sets the name of the class to be executed.
 256      * @param mainClass the name of the class
 257      * @return this task object
 258      */
 259     public JavaTask mainClass(String mainClass) {
 260         this.mainClass = mainClass;
 261         return this;
 262     }
 263 
 264     /**
 265      * Sets the name of the module which contains class to be executed. If empty,
 266      * the main class is assumed to be in the unnamed module.
 267      * @param module the name of the module
 268      * @return this task object
 269      */
 270     public JavaTask module(String module) {
 271         this.module = module;
 272         return this;
 273     }
 274 
 275     /**
 276      * Sets the name of the module and the main class name to be executed.
 277      * @param module the name of the module
 278      * @param mainClass the name of the class
 279      * @return this task object
 280      */
 281     public JavaTask module(String module, String mainClass) {
 282         this.module = module;
 283         this.mainClass = mainClass;
 284         return this;
 285     }
 286 
 287     /**
 288      * Sets the arguments for the class to be executed.
 289      * @param classArgs the arguments
 290      * @return this task object
 291      */
 292     public JavaTask classArgs(String... classArgs) {
 293         this.classArgs = Arrays.asList(classArgs);
 294         return this;
 295     }
 296 
 297     /**
 298      * Sets that the standard VM and java options would not be passed
 299      * to the new VM instance. If this method is not called, the default behavior
 300      * is that the options will be passed to the new VM instance.
 301      *
 302      * @return this task object
 303      */
 304     public JavaTask ignoreStandardOptions() {
 305         standardOptions = EMPTY_LIST;
 306         return this;
 307     }
 308 
 309     /**
 310      * Filters the standard VM and java options.
 311      * @param filter a filter
 312      * @return this task object
 313      */
 314     public JavaTask filterStandardOption(Function<List<String>, List<String>> filter) {
 315         standardOptions = filter.apply(standardOptions);
 316         return this;
 317     }
 318 
 319     /**
 320      * Filters the standard VM and java options by taking away a particular option
 321      * and a specified number or parameters after it.
 322      *
 323      * @param option the option, such as {@code --limit-modules}
 324      * @param parameterCount number of parameters to remove after the option.
 325      * @return this task object
 326      */
 327     public JavaTask ignoreStandardOption(String option, int parameterCount) {
 328         return filterStandardOption(os -> {
 329             List<String> filtered = new ArrayList<>();
 330             for(int i = 0; i < os.size(); i++) {
 331                 if(os.get(i).equals(option))
 332                     i += parameterCount;
 333                 else
 334                     filtered.add(os.get(i));
 335             }
 336             return filtered;
 337         });
 338     }
 339 
 340     /**
 341      * Filters the standard VM and java options so that there are no options which
 342      * affects a set of resolvable modules or a set of accessible APIs.
 343      *
 344      * @return this task object
 345      */
 346     public JavaTask ignoreStandardModuleOptions() {
 347         moduleOptions.entrySet().stream()
 348                 .forEach(e -> ignoreStandardOption(e.getKey(), e.getValue()));
 349         return this;
 350     }
 351 
 352     /**
 353      * {@inheritDoc}
 354      * @return the name "java"
 355      */
 356     @Override
 357     public String name() {
 358         return "java";
 359     }
 360 
 361     /**
 362      * Calls the Java launcher with the arguments as currently configured.
 363      * @return a Result object indicating the outcome of the task
 364      * and the content of any output written to stdout or stderr.
 365      * @throws TaskError if the outcome of the task is not as expected.
 366      */
 367     @Override
 368     public Task.Result run() {
 369         List<String> args = new ArrayList<>(standardOptions);
 370         if (classPath != null) {
 371             args.add(CLASS_PATH);
 372             args.add(classPath.stream().collect(joining(",")));
 373         }
 374         if (modulePath != null) {
 375             args.add(MODULE_PATH);
 376             args.add(modulePath.stream().collect(joining(",")));
 377         }
 378         if (upgradeModulePath != null) {
 379             args.add(UPGRADE_MODULE_PATH);
 380             args.add(upgradeModulePath.stream().collect(joining(",")));
 381         }
 382         if (addExports != null)
 383             addExports.stream().forEach(s -> {
 384                 args.add(ADD_EXPORTS);
 385                 args.add(s);
 386             });
 387         if (addReads != null)
 388             addReads.stream().forEach(s -> {
 389                 args.add(ADD_READS);
 390                 args.add(s);
 391             });
 392         if (patchModule != null)
 393             patchModule.stream().forEach(s -> {
 394                 args.add(PATCH_MODULE);
 395                 args.add(s);
 396             });
 397         if (addModules != null) {
 398             args.add(ADD_MODULES);
 399             args.add(addModules.stream().collect(joining(",")));
 400         }
 401         if (limitModules != null) {
 402             args.add(LIMIT_MODULES);
 403             args.add(limitModules.stream().collect(joining(",")));
 404         }
 405         if (vmOptions != null)
 406             args.addAll(vmOptions);
 407         if (mainClass != null)
 408             if (module != null) {
 409                 args.add("-m");
 410                 args.add(module + "/" + mainClass);
 411             } else
 412                 args.add(mainClass);
 413         if (classArgs != null)
 414             args.addAll(classArgs);
 415         return run(Tool.JAVA, args);
 416     }
 417 
 418     private void patchModule(String module, Stream<String> files) {
 419         this.patchModule.add(module + "=" +
 420                 files.collect(joining(",")));
 421     }
 422 
 423     //this is a list of options to ignore in ignoreStandardModuleOptions()
 424     private static final Map<String, Integer> moduleOptions =
 425             Map.of(LIMIT_MODULES, 1,
 426                     UPGRADE_MODULE_PATH, 1,
 427                     ADD_MODULES, 1,
 428                     ADD_READS, 1,
 429                     ADD_EXPORTS, 1,
 430                     PATCH_MODULE, 1);
 431 }