1 /*
   2  * Copyright (c) 2019, 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 package jdk.jpackage.test;
  24 
  25 import java.io.File;
  26 import java.io.IOException;
  27 import java.nio.file.Files;
  28 import java.nio.file.Path;
  29 import java.util.ArrayList;
  30 import java.util.List;
  31 import java.util.concurrent.atomic.AtomicBoolean;
  32 import java.util.regex.Matcher;
  33 import java.util.regex.Pattern;
  34 import java.util.stream.Collectors;
  35 import jdk.jpackage.test.Functional.ThrowingFunction;
  36 import jdk.jpackage.test.Functional.ThrowingSupplier;
  37 
  38 public class HelloApp {
  39 
  40     HelloApp(JavaAppDesc appDesc) {
  41         if (appDesc == null) {
  42             this.appDesc = createDefaltAppDesc();
  43         } else {
  44             this.appDesc = appDesc;
  45         }
  46     }
  47 
  48     private JarBuilder prepareSources(Path srcDir) throws IOException {
  49         final String qualifiedClassName = appDesc.className();
  50 
  51         final String className = qualifiedClassName.substring(
  52                 qualifiedClassName.lastIndexOf('.') + 1);
  53         final String packageName = appDesc.packageName();
  54 
  55         final Path srcFile = srcDir.resolve(Path.of(String.join(
  56                 File.separator, qualifiedClassName.split("\\.")) + ".java"));
  57         Files.createDirectories(srcFile.getParent());
  58 
  59         JarBuilder jarBuilder = createJarBuilder().addSourceFile(srcFile);
  60         final String moduleName = appDesc.moduleName();
  61         if (moduleName != null) {
  62             Path moduleInfoFile = srcDir.resolve("module-info.java");
  63             TKit.createTextFile(moduleInfoFile, List.of(
  64                     String.format("module %s {", moduleName),
  65                     String.format("    exports %s;", packageName),
  66                     "    requires java.desktop;",
  67                     "}"
  68             ));
  69             jarBuilder.addSourceFile(moduleInfoFile);
  70             jarBuilder.setModuleVersion(appDesc.moduleVersion());
  71         }
  72 
  73         // Add package directive and replace class name in java source file.
  74         // Works with simple test Hello.java.
  75         // Don't expect too much from these regexps!
  76         Pattern classNameRegex = Pattern.compile("\\bHello\\b");
  77         Pattern classDeclaration = Pattern.compile(
  78                 "(^.*\\bclass\\s+)\\bHello\\b(.*$)");
  79         Pattern importDirective = Pattern.compile(
  80                 "(?<=import (?:static )?+)[^;]+");
  81         AtomicBoolean classDeclared = new AtomicBoolean();
  82         AtomicBoolean packageInserted = new AtomicBoolean(packageName == null);
  83 
  84         var packageInserter = Functional.identityFunction((line) -> {
  85             packageInserted.setPlain(true);
  86             return String.format("package %s;%s%s", packageName,
  87                     System.lineSeparator(), line);
  88         });
  89 
  90         Files.write(srcFile, Files.readAllLines(HELLO_JAVA).stream().map(line -> {
  91             Matcher m;
  92             if (classDeclared.getPlain()) {
  93                 if ((m = classNameRegex.matcher(line)).find()) {
  94                     line = m.replaceAll(className);
  95                 }
  96                 return line;
  97             }
  98 
  99             if (!packageInserted.getPlain() && importDirective.matcher(line).find()) {
 100                 line = packageInserter.apply(line);
 101             } else if ((m = classDeclaration.matcher(line)).find()) {
 102                 classDeclared.setPlain(true);
 103                 line = m.group(1) + className + m.group(2);
 104                 if (!packageInserted.getPlain()) {
 105                     line = packageInserter.apply(line);
 106                 }
 107             }
 108             return line;
 109         }).collect(Collectors.toList()));
 110 
 111         return jarBuilder;
 112     }
 113 
 114     private JarBuilder createJarBuilder() {
 115         JarBuilder builder = new JarBuilder();
 116         if (appDesc.jarWithMainClass()) {
 117             builder.setMainClass(appDesc.className());
 118         }
 119         return builder;
 120     }
 121 
 122     void addTo(JPackageCommand cmd) {
 123         final String moduleName = appDesc.moduleName();
 124         final String jarFileName = appDesc.jarFileName();
 125         final String qualifiedClassName = appDesc.className();
 126 
 127         if (moduleName != null && appDesc.packageName() == null) {
 128             throw new IllegalArgumentException(String.format(
 129                     "Module [%s] with default package", moduleName));
 130         }
 131 
 132         if (moduleName == null && CLASS_NAME.equals(qualifiedClassName)) {
 133             // Use Hello.java as is.
 134             cmd.addAction((self) -> {
 135                 Path jarFile = self.inputDir().resolve(jarFileName);
 136                 createJarBuilder().setOutputJar(jarFile).addSourceFile(
 137                         HELLO_JAVA).create();
 138             });
 139         } else {
 140             cmd.addAction((self) -> {
 141                 final Path jarFile;
 142                 if (moduleName == null) {
 143                     jarFile = self.inputDir().resolve(jarFileName);
 144                 } else {
 145                     // `--module-path` option should be set by the moment
 146                     // when this action is being executed.
 147                     jarFile = Path.of(self.getArgumentValue("--module-path",
 148                             () -> self.inputDir().toString()), jarFileName);
 149                     Files.createDirectories(jarFile.getParent());
 150                 }
 151 
 152                 TKit.withTempDirectory("src",
 153                         workDir -> prepareSources(workDir).setOutputJar(jarFile).create());
 154             });
 155         }
 156 
 157         if (moduleName == null) {
 158             cmd.addArguments("--main-jar", jarFileName);
 159             cmd.addArguments("--main-class", qualifiedClassName);
 160         } else {
 161             cmd.addArguments("--module-path", TKit.workDir().resolve(
 162                     "input-modules"));
 163             cmd.addArguments("--module", String.join("/", moduleName,
 164                     qualifiedClassName));
 165             // For modular app assume nothing will go in input directory and thus
 166             // nobody will create input directory, so remove corresponding option
 167             // from jpackage command line.
 168             cmd.removeArgumentWithValue("--input");
 169         }
 170         if (TKit.isWindows()) {
 171             cmd.addArguments("--win-console");
 172         }
 173     }
 174 
 175     static JavaAppDesc createDefaltAppDesc() {
 176         return new JavaAppDesc().setClassName(CLASS_NAME).setJarFileName(
 177                 "hello.jar");
 178     }
 179 
 180     static void verifyOutputFile(Path outputFile, List<String> args) {
 181         if (!outputFile.isAbsolute()) {
 182             verifyOutputFile(outputFile.toAbsolutePath().normalize(), args);
 183             return;
 184         }
 185 
 186         TKit.assertFileExists(outputFile);
 187 
 188         List<String> contents = ThrowingSupplier.toSupplier(
 189                 () -> Files.readAllLines(outputFile)).get();
 190 
 191         List<String> expected = new ArrayList<>(List.of(
 192                 "jpackage test application",
 193                 String.format("args.length: %d", args.size())
 194         ));
 195         expected.addAll(args);
 196 
 197         TKit.assertStringListEquals(expected, contents, String.format(
 198                 "Check contents of [%s] file", outputFile));
 199     }
 200 
 201     public static void executeLauncherAndVerifyOutput(JPackageCommand cmd) {
 202         final Path launcherPath = cmd.appLauncherPath();
 203         if (!cmd.isFakeRuntime(String.format("Not running [%s] launcher",
 204                 launcherPath))) {
 205             executeAndVerifyOutput(launcherPath, cmd.getAllArgumentValues(
 206                     "--arguments"));
 207         }
 208     }
 209 
 210     public static void executeAndVerifyOutput(Path helloAppLauncher,
 211             String... defaultLauncherArgs) {
 212         executeAndVerifyOutput(helloAppLauncher, List.of(defaultLauncherArgs));
 213     }
 214 
 215     public static void executeAndVerifyOutput(Path helloAppLauncher,
 216             List<String> defaultLauncherArgs) {
 217         // Output file will be created in the current directory.
 218         Path outputFile = TKit.workDir().resolve(OUTPUT_FILENAME);
 219         ThrowingFunction.toFunction(Files::deleteIfExists).apply(outputFile);
 220         new Executor()
 221                 .setDirectory(outputFile.getParent())
 222                 .setExecutable(helloAppLauncher)
 223                 .dumpOutput()
 224                 .execute()
 225                 .assertExitCodeIsZero();
 226 
 227         verifyOutputFile(outputFile, defaultLauncherArgs);
 228     }
 229 
 230     final static String OUTPUT_FILENAME = "appOutput.txt";
 231 
 232     private final JavaAppDesc appDesc;
 233 
 234     private static final Path HELLO_JAVA = TKit.TEST_SRC_ROOT.resolve(
 235             "apps/image/Hello.java");
 236 
 237     private final static String CLASS_NAME = HELLO_JAVA.getFileName().toString().split(
 238             "\\.", 2)[0];
 239 }