public class ExcelsiorJet {

    private final JetHome jetHome;
    private final Log logger;

    private JetEdition edition;
    private OS targetOS;
    private CpuArch targetCpu;

    public ExcelsiorJet(JetHome jetHome, Log logger) throws JetHomeException {
        this.jetHome = jetHome;
        this.logger = logger;
        detectEditionAndTargetPlatform();
    }

    public ExcelsiorJet(String jetHome) throws JetHomeException {
        this(Utils.isEmpty(jetHome) ? new JetHome() : new JetHome(jetHome), Log.logger);
    }

    /**
     * Invokes the {@code jc} command line tool in the given {@code workingDirectory} with a logger specified at construction time, passing
     * {@code args} to it.
     *
     * @param workingDirectory working directory for {@code jc}. May be null, in which case the working directory of the current process is used.
     * @param args command line arguments that will be passed to {@code jc}.
     */
    public int compile(File workingDirectory, String... args) throws CmdLineToolException {
        return new JetCompiler(jetHome, args)
                .workingDirectory(workingDirectory)
                .withLog(logger)
                .execute();
    }

    /**
     * Invokes the {@code xpack} command line tool in the given {@code workingDirectory} with a logger specified at construction time, passing
     * {@code args} to it.
     *
     * @param workingDirectory working directory for {@code xpack}. May be null, in which case the working directory of the current process is used.
     * @param args command line arguments that will be passed to {@code xpack}.
     */
    public int pack(File workingDirectory, String... args) throws CmdLineToolException {
        return new JetPackager(jetHome, args)
                .workingDirectory(workingDirectory)
                .withLog(logger)
                .execute();
    }

    /**
     * Invokes the {@code xjava} command line tool in the given {@code workingDirectory} with a logger specified at construction time, passing
     * {@code args} to it.
     *
     * @param workingDirectory working directory for {@code xjava}. May be null, in which case the working directory of the current process is used.
     * @param args command line arguments that will be passed to {@code xjava}.
     */
    public int testRun(File workingDirectory, String... args) throws CmdLineToolException {
        return testRun(workingDirectory, logger, false, args);
    }

    /**
     * Invokes the {@code xjava} command line tool in the given {@code workingDirectory} with the specified {@code logger}, passing
     * {@code args} to it.
     *
     * @param workingDirectory working directory for {@code xjava}. May be null, in this case current process's working directory is used.
     * @param logger logger to which the output of {@code xjava} should be redirected.
     * @param errToOut specifies whether the stderr of xjava should be redirected with info level.
     * @param args command line arguments that will be passed to {@code xjava}.
     */
    public int testRun(File workingDirectory, Log logger, boolean errToOut, String... args) throws CmdLineToolException {
        return new XJava(jetHome, args)
                .workingDirectory(workingDirectory)
                .withLog(logger, errToOut)
                .execute();
    }

    private String obtainVersionString() throws JetHomeException {
        try {
            String[] result = {null};
            CmdLineTool jetCompiler = new JetCompiler(this.jetHome).withLog(new StdOutLog() {
                public void info(String info) {
                    if (result[0] == null) {
                        if (info.contains("Excelsior JET ")) {
                            result[0] = info;
                        }
                    }
                }

            });
            if ((jetCompiler.execute() != 0) || result[0] == null)  {
                throw new JetHomeException(Txt.s("JetHome.UnableToDetectEdition.Error"));
            }
            return result[0];
        } catch (CmdLineToolException e) {
            throw new JetHomeException(e.getMessage());
        }
    }

    private void detectEditionAndTargetPlatform() throws JetHomeException {
        if (edition == null) {
            String version = obtainVersionString();
            edition = JetEdition.retrieveEdition(version);
            if (edition == null) {
                throw new JetHomeException(Txt.s("JetHome.UnableToDetectEdition.Error"));
            }

            targetOS = Host.getOS();

            if (version.contains("64-bit")) {
                targetCpu = CpuArch.AMD64;
            } else if (version.contains("ARM")) {
                targetCpu = CpuArch.ARM32;
                //currently Excelsior JET supports only ARM Linux
                targetOS = OS.LINUX;
            } else {
                targetCpu = CpuArch.X86;
            }
        }
    }

    public JetEdition getEdition() {
        return edition;
    }

    public OS getTargetOS()  {
        return targetOS;
    }

    private boolean isX86() {
        return targetCpu == CpuArch.X86;
    }

    public boolean isGlobalOptimizerSupported() {
        return (isX86() || since12_0()) && (getEdition() != JetEdition.STANDARD);
    }

    public boolean isSlimDownSupported() {
        return isGlobalOptimizerSupported() && isX86();
    }

    public boolean isUsageListGenerationSupported() {
        return isX86() || since12_0();
    }

    public boolean isStartupProfileGenerationSupported()  {
        return getEdition() != JetEdition.STANDARD;
    }

    private boolean isFullFeaturedEdition() {
        return (edition == JetEdition.EVALUATION) || (edition == JetEdition.ENTERPRISE);
    }

    private boolean isEmbedded() {
        return (edition == JetEdition.EMBEDDED) || (edition == JetEdition.EMBEDDED_EVALUATION);
    }

    public boolean isTomcatSupported() {
        return  isFullFeaturedEdition() ||
                since11_3() && isEmbedded();
    }

    public boolean isSpringBootSupported() {
        return  since15_3() &&
                (isFullFeaturedEdition() || isEmbedded());
    }

    public boolean isPDBConfigurationSupported() {
        return since15_0();
    }

    public boolean isSmartSupported() {
        return since15_0() && !isX86();
    }
    public boolean since11_3() {
        return jetHome.getJetVersion() >= 1130;
    }

    public boolean since12_0() {
        return jetHome.getJetVersion() >= 1200;
    }

    public boolean since15_0() {
        return jetHome.getJetVersion() >= 1500;
    }

    public boolean since15_3() {
        return jetHome.getJetVersion() >= 1530;
    }

    public boolean isCrossCompilation() {
        //currently only ARM32 is supported in cross compilation mode
        return targetCpu == CpuArch.ARM32;
    }

    public boolean isTestRunSupported() {
        return !isCrossCompilation();
    }

    public boolean isExcelsiorInstallerSupported() {
        return !getTargetOS().isOSX() && !(isEmbedded());
    }

    public boolean isAdvancedExcelsiorInstallerFeaturesSupported() {
        return isExcelsiorInstallerSupported() && since11_3() && (edition != JetEdition.STANDARD);
    }

    public boolean isStartupAcceleratorSupported() {
        return !getTargetOS().isOSX() && !isCrossCompilation() && (edition != JetEdition.STANDARD);
    }

    public boolean isWindowsServicesSupported() {
        return targetOS.isWindows() && (edition != JetEdition.STANDARD);
    }

    public boolean isWindowsServicesInExcelsiorInstallerSupported() {
        return isWindowsServicesSupported() && isExcelsiorInstallerSupported() && since11_3();
    }

    public boolean isCompactProfilesSupported() {
        return since11_3() && (edition != JetEdition.STANDARD);
    }

    public boolean isMultiAppSupported() {
        return edition != JetEdition.STANDARD;
    }

    public boolean isTrialSupported() {
        return edition != JetEdition.STANDARD;
    }

    public boolean isDataProtectionSupported() {
        return edition != JetEdition.STANDARD;
    }

    public boolean isDiskFootprintReductionSupported() {
        return since11_3() && isGlobalOptimizerSupported();
    }

    public boolean isHighDiskFootprintReductionSupported() {
        return isDiskFootprintReductionSupported() && isX86();
    }

    public boolean isWindowsVersionInfoSupported() {
        return targetOS.isWindows() && (edition != JetEdition.STANDARD);
    }

    public boolean isRuntimeSupported(RuntimeFlavorType flavor) {
        switch (flavor) {
            case SERVER:
                return isFullFeaturedEdition() ||
                        (since11_3() && isEmbedded());
            case DESKTOP:
                return edition != JetEdition.STANDARD;
            case CLASSIC:
                return true;
            default:
                throw new AssertionError("Unknown runtime flavor:" + flavor);
        }
    }

    public boolean isChangeRTLocationAvailable() {
        return since11_3();
    }

    public boolean isPGOSupported() {
        return since12_0() && !isX86() && (edition != JetEdition.STANDARD) && (edition != JetEdition.PROFESSIONAL);
    }

    /**
     * @return home directory of this Excelsior JET instance
     */
    public String getJetHome() {
        return jetHome.getJetHome();
    }

} public enum JetEdition {
    EVALUATION("Evaluation"),
    STANDARD("Standard Edition"),
    PROFESSIONAL("Professional Edition"),
    ENTERPRISE("Enterprise Edition"),
    EMBEDDED("Embedded Edition"),
    EMBEDDED_EVALUATION("Embedded Evaluation");

    private final String fullName;

    JetEdition(String fullName) {
        this.fullName = fullName;
    }

    /**
     * Examples: "Enterprise Edition", "Embedded Evaluation".
     */
    public String fullEditionName() {
        return fullName;
    }

    static JetEdition retrieveEdition(String version) {
        return Arrays.stream(JetEdition.values())
                .filter(e -> version.contains(e.fullEditionName()))
                .findFirst()
                .orElse(null);
    }
} public class StdOutLog extends Log {

    @Override
    public void debug(String msg, Throwable t) {
        System.out.println(msg);
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        t.printStackTrace(new PrintStream(buffer));
        System.out.printf(new String(buffer.toByteArray(), 0, buffer.size()));
    }

    @Override
    public void info(String msg) {
        System.out.println(msg);
    }

    @Override
    public void warn(String msg) {
        System.out.println(msg);
    }

    @Override
    public void warn(String msg, Throwable t) {
        System.out.println(msg);
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        t.printStackTrace(new PrintStream(buffer));
        System.out.printf(new String(buffer.toByteArray(), 0, buffer.size()));
    }

    @Override
    public void error(String msg) {
        System.err.println(msg);
    }

} public enum OS {
    WINDOWS,
    LINUX,
    OSX;

    public boolean isWindows() {
        return this == WINDOWS;
    }

    public boolean isLinux() {
        return this == LINUX;
    }

    public boolean isOSX() {
        return this == OSX;
    }

    public boolean isUnix() {
        return this == LINUX ||  this == OSX;
    }

    public String getExeFileExtension() {
        if (isWindows()) {
            return ".exe";
        } else if (isUnix()) {
            return "";
        } else {
            throw new AssertionError("Unknown OS: " + this);
        }
    }

    public String getDllFileExtension() {
        switch (this) {
            case WINDOWS:   return ".dll";
            case LINUX:     return ".so";
            case OSX:       return ".dylib";
            default:
                throw new AssertionError("Unknown OS: " + this);
        }
    }

    public String getDllFilePrefix() {
        switch (this) {
            case WINDOWS:   return "";
            case LINUX:     return "lib";
            case OSX:       return "lib";
            default:
                throw new AssertionError("Unknown OS: " + this);
        }
    }


    public String mangleExeName(String exe) {
        return exe + getExeFileExtension();
    }

    public String mangleDllName(String dll, boolean addPrefix) {
        return (addPrefix ? getDllFilePrefix() : "") + dll + getDllFileExtension();
    }

    public String mangleDllName(String dll) {
        return mangleDllName(dll, true);
    }
} public enum ApplicationType {

    /**
     * Plain Java application that runs standalone.
     */
    PLAIN,

    /**
     * Spring Boot application that runs as Spring Boot executable jar.
     */
    SPRING_BOOT,

    /**
     * Servlet-based Java application that runs within the Tomcat servlet container.
     */
    TOMCAT,

    /**
     * Dynamic library callable from a non-Java environment.
     */
    DYNAMIC_LIBRARY,

    /**
     * Windows service (Windows only).
     */
    WINDOWS_SERVICE
    ;

    public String toString() {
        return Utils.enumConstantNameToParameter(name());
    }

    public static ApplicationType validate(String appType) throws JetTaskFailureException {
        try {
            return ApplicationType.valueOf(Utils.parameterToEnumConstantName(appType));
        } catch (Exception e) {
            throw new JetTaskFailureException(s("JetApi.UnknownAppType.Failure", appType));
        }
    }
} public class ExecProfilesConfig {

    private static final String PROFILE_DIR = "appToProfile";

    /**
     * The target location for application execution profiles gathered by the Test Run and Profile tasks.
     * It is recommended to commit the collected profiles ({@code .usg}, {@code .startup}, {@code .jprof})
     * to the VCS to enable the {@link JetBuildTask} to re-use them during subsequent builds without performing
     * a Test Run and/or Profile task again. Once you have committed the profiles, it is recommended to set
     * the {@link #checkExistence} parameter to an appropriate value in order to verify that the profiles
     * are available during subsequent builds.
     * <p>
     * By default, {@link JetProject#jetResourcesDir} is used.
     * </p>
     * @see TestRunTask
     * @see RunTask
     */
    public File outputDir;

    /**
     * The base file name of all execution profiles. By default, {@link JetProject#projectName} is used.
     */
    public String outputName;

    /**
     * <p>
     * Whether to run the Profile task on the same machine or prepare an image for deployment
     * to a reference system.
     * </p>
     * <p>
     * It may be necessary to profile the natively compiled application on a computer other than the one
     * conducting the build, e.g. because profiling requires a specially configured environment.
     * Setting this parameter to {@code false} forces the plugin to create a special <em>profiling image</em>
     * that you can then deploy to such an environment to collect an application execution profile.
     * </p>
     * <p>
     * You can also set the {@code jet.create.profiling.image} system property to force the Profile task to create
     * such an image instead of running the generated binary locally.
     * </p>
     * <p>
     * Note that this parameter is always set to {@code false} for the cross-compiling flavors of Excelsior JET,
     * e.g. those targeting Linux/ARM.
     * </p>
     *
     * @see #profilingImageDir
     */
    public Boolean profileLocally;

    /**
     * <p>
     * Directory where the special "profiling" image of the natively compiled application has to be placed.
     * </p>
     * <p>
     * By default, points to the {@code "appToProfile"} subdirectory of {@link JetProject#jetOutputDir}.
     * </p>
     * <p>
     * To facilitate deployment of the profiling image to a reference system when {@link #profileLocally}
     * is set to {@code false}, the plugin also creates a zip archive that contains a copy of that image,
     * gives it the same base name and places it next to this directory.
     * </p>
     */
    public File profilingImageDir;

    /**
     * <p>
     * Profile validity threshold in days.
     * </p>
     * <p>
     * It is recommended to re-collect all profiles periodically as your code base evolves.
     * The plugins issue a warning upon detecting an outdated profile during a build.
     * With this parameter, you can adjust the respective threshold, measured in days,
     * or set it to {@code 0} to disable the warning. The default value is 30.
     * </p>
     */
    public int daysToWarnAboutOutdatedProfiles = 30;

    /**
     * Check that all or certain application profiles are available before starting a build.
     * Valid values are: "all" , "test-run", "profile", "none" (default):
     * <p>
     * {@code test-run} - profiles collected by the Test Run task (".usg", ".startup").
     * </p>
     * <p>
     * {@code profile} - application executiion profile collected by the Profile task (".jprof").
     * </p>
     * <p>
     * {@code all} - all profiles (".usg", ".startup", and ".jprof").
     * </p>
     */
    public String checkExistence = ExecProfilesExistenceType.NONE.toString();

    /**
     * Timeout for the test run task in seconds.
     *
     * Set the parameter, if your app does not terminate automatically and you would like to automate collection of
     * the profiles gathered during {@link TestRunTask}.
     */
    public int testRunTimeout;

    /**
     * Timeout for the profile run task in seconds.
     *
     * Set the parameter, if your app does not terminate automatically and you would like to automate collection of
     * the execution profile gathered during jet:profile task.
     */
    public int profileRunTimeout;

    public void fillDefaults(JetProject jetProject, ExcelsiorJet excelsiorJet) throws JetTaskFailureException {
        if (outputDir == null) {
            outputDir = jetProject.jetResourcesDir();
        }
        if (Utils.isEmpty(outputName)) {
            outputName = jetProject.projectName();
        }

        if (excelsiorJet.isCrossCompilation())  {
            if ((profileLocally != null) && profileLocally) {
                logger.warn(s("JetApi.CannotProfileLocallyForCrossCompilation.Warning"));
            }
            profileLocally = false;
        } else if (System.getProperty("jet.create.profiling.image") != null) {
            profileLocally = false;
        } else if (profileLocally == null) {
            profileLocally = true;
        }

        if (profilingImageDir == null) {
            profilingImageDir = new File(jetProject.jetOutputDir(), PROFILE_DIR);
        }

        ExecProfilesExistenceType existenceType = ExecProfilesExistenceType.validate(checkExistence);
        if ((existenceType == ALL) || (existenceType == TEST_RUN)) {
            if (excelsiorJet.isUsageListGenerationSupported() && !getUsg().exists()) {
                throw new JetTaskFailureException(s("JetApi.NoTestRunProfile.Failure", getUsg().getAbsolutePath()));
            }
            if (excelsiorJet.isStartupProfileGenerationSupported() && !getStartup().exists()) {
                throw new JetTaskFailureException(s("JetApi.NoTestRunProfile.Failure", getStartup().getAbsolutePath()));
            }
        }
        if ((existenceType == ALL) || (existenceType == PROFILE)) {
            if (excelsiorJet.isPGOSupported() && !getJProfile().exists()) {
                throw new JetTaskFailureException(s("JetApi.NoJProfile.Failure", getJProfile().getAbsolutePath()));
            }
        }
    }


    public File getUsg() {
        return new File(outputDir, outputName + ".usg");
    }

    public File getStartup() {
        return new File(outputDir, outputName + ".startup");
    }

    public File getJProfile() {
        return new File(outputDir, outputName + ".jprof");
    }

} public class WindowsVersionInfoConfig {

    /**
     * Company name for the version-information resource.
     *
     * By default, {@link JetProject#vendor} is used.
     */
    public String company;

    /**
     * Product name for the version-information resource.
     *
     * By default, {@link JetProject#product} is used.
     */
    public String product;

    /**
     * Version number string for the version-information resource.
     * (Both {@code ProductVersion} and {@code FileVersion} resource strings are set to the same value.)
     * Must have {@code v1.v2.v3.v4} format where {@code vi} is a number.
     * If not set, {@code ${project.version}} is used. If the value does not meet the required format,
     * it is coerced. For instance, "1.2.3-SNAPSHOT" becomes "1.2.3.0"
     */
    public String version;

    /**
     * Legal copyright notice string for the version-information resource.
     * By default, {@code "Copyright © {$project.inceptionYear},[curYear] [vendor]"} is used.
     */
    public String copyright;

    /**
     * File description string for the version-information resource.
     *
     * The value of {@link #product} is used by default.
     */
    public String description;

    public void fillDefaults(JetProject jetProject) {
        if (company == null) {
            company = jetProject.vendor();
        }

        if (product == null) {
            product = jetProject.product();
        }

        if (version == null) {
            version = jetProject.version();
        }

        //Coerce winVIVersion to v1.v2.v3.v4 format.
        String finalVersion = Utils.deriveFourDigitVersion(version);
        if (!version.equals(finalVersion)) {
            logger.warn(s("JetApi.NotCompatibleExeVersion.Warning", version, finalVersion));
            version = finalVersion;
        }

        if (copyright == null) {
            String inceptionYear = jetProject.inceptionYear();
            String curYear = new SimpleDateFormat("yyyy").format(new Date());
            String years = Utils.isEmpty(inceptionYear) ? curYear : inceptionYear + "," + curYear;
            copyright = "Copyright \\x00a9 " + years + " " + company;
        }
        if (description == null) {
            description = product;
        }
    }

    public boolean isEmpty() {
        return Stream.of(company, product, version, copyright, description).allMatch(Objects::isNull);
    }
} public class RuntimeConfig {

    /**
     * Excelsior JET Runtime flavor.
     * <p>
     * Excelsior JET comes with multiple implementations of the runtime system,
     * optimized for different hardware configurations and application types.
     * For details, refer to the Excelsior JET User's Guide, Chapter "Application
     * Considerations", section "Runtime Selection".
     * </p>
     * <p>
     * Available runtime flavors: {@code desktop}, {@code server}, {@code classic}
     * </p>
     */
    public String flavor;

    /**
     * Location of Excelsior JET runtime files in the installation package.
     * By default, Excelsior JET places its runtime files required for the 
     * generated executable to work in a folder named {@code "rt"} located next to that executable.
     * You may change that default location with this parameter.
     * <p>
     * This functionality is only available in Excelsior JET 11.3 and above.
     * </p>
     */
    public String location;

    /**
     * Optional JET Runtime components that have to be added to the package.
     * By default, only the {@code jce} component (Java Crypto Extension) is added.
     * You may pass a special value {@code all} to include all available optional components at once
     * or {@code none} to not include any of them.
     * <p>
     * Available optional components:
     * {@code runtime_utilities}, {@code fonts}, {@code awt_natives}, {@code api_classes}, {@code jce},
     * {@code accessibility}, {@code javafx}, {@code javafx-webkit}, {@code nashorn}, {@code cldr}
     * </p>
     */
    public String[] components;

    /**
     * Locales and charsets that have to be included in the package.
     * By default only {@code European} locales are added.
     * You may pass a special value {@code all} to include all available locales at once
     * or {@code none} to not include any additional locales.
     * <p>
     * Available locales and charsets:
     *    {@code European}, {@code Indonesian}, {@code Malay}, {@code Hebrew}, {@code Arabic},
     *    {@code Chinese}, {@code Japanese}, {@code Korean}, {@code Thai}, {@code Vietnamese}, {@code Hindi},
     *    {@code Extended_Chinese}, {@code Extended_Japanese}, {@code Extended_Korean}, {@code Extended_Thai},
     *    {@code Extended_IBM}, {@code Extended_Macintosh}, {@code Latin_3}
     * </p>
     */
    public String[] locales;

    /**
     * Java SE API subset to be included in the package.
     * Java SE 8 defines three subsets of the standard Platform API called compact profiles.
     * Excelsior JET enables you to deploy your application with one of those subsets.
     * You may set this parameter to specify a particular profile.
     * Valid values are: {@code auto} (default),  {@code compact1},  {@code compact2},  {@code compact3}, {@code full}
     * <p>
     * {@code auto} value (default) forces Excelsior JET to detect which parts of the Java SE Platform API
     * are referenced by the application and select the smallest compact profile that includes them all,
     * or the entire Platform API if there is no such profile.
     * </p>
     * This functionality is available for Excelsior JET 11.3 and above.
     */
    public String profile;

    /**
     * Disk footprint reduction mode.
     * Excelsior JET can reduce the disk footprint of the application by including the supposedly
     * unused Java SE API classes in the resulting package in a compressed form.
     * Valid values are: {@code none},  {@code medium} (default),  {@code high-memory},  {@code high-disk}.
     * <p>
     * This feature is only available if {@link JetProject#globalOptimizer} is enabled.
     * In this mode, the Java SE classes that were not compiled into the resulting executable are placed
     * into the resulting package in bytecode form, possibly compressed depending on the mode:
     * </p>
     * <dl>
     * <dt>none</dt>
     * <dd>Disable compression</dd>
     * <dt>medium</dt>
     * <dd>Use a simple compression algorithm that has minimal run time overheads and permits
     * selective decompression.</dd>
     * <dt>high-memory</dt>
     * <dd>(32-bit only) Compress all unused Java SE API classes as a whole. This results in more significant disk
     * footprint reduction compared to the {@code medium} compression. However, if one of the compressed classes
     * is needed at run time, the entire bundle must be decompressed to retrieve it.
     * In the {@code high-memory} reduction mode the bundle is decompressed
     * onto the heap and can be garbage collected later.</dd>
     * <dt>high-disk</dt>
     * <dd>(32-bit only) Same as {@code high-memory}, but decompress to the temp directory.</dd>
     * </dl>
     */
    public String diskFootprintReduction;

    /**
     * (32-bit only) Java Runtime Slim-Down configuration parameters.
     *
     * @see SlimDownConfig#detachedBaseURL
     * @see SlimDownConfig#detachComponents
     * @see SlimDownConfig#detachedPackage
     */
    public SlimDownConfig slimDown;

    public void fillDefaults(JetProject jetProject, ExcelsiorJet excelsiorJet) throws JetTaskFailureException {

        if (flavor != null) {
            RuntimeFlavorType flavorType = RuntimeFlavorType.validate(flavor);
            if (!excelsiorJet.isRuntimeSupported(flavorType)) {
                throw new JetTaskFailureException(s("JetApi.RuntimeKindNotSupported.Failure", flavor));
            }
        }

        if (location != null) {
            if (!excelsiorJet.isChangeRTLocationAvailable()) {
                throw new JetTaskFailureException(s("JetApi.RuntimeLocationNotAvailable.Failure", flavor));
            }
        }

        if (profile == null) {
            profile = CompactProfileType.AUTO.toString();
        } else {
            CompactProfileType compactProfile = CompactProfileType.validate(profile);
            if (!excelsiorJet.isCompactProfilesSupported()) {
                switch (compactProfile) {
                    case COMPACT1: case COMPACT2: case COMPACT3:
                        throw new JetTaskFailureException(s("JetApi.CompactProfilesNotSupported.Failure", profile));
                    case AUTO: case FULL:
                        break;
                    default:  throw new AssertionError("Unknown compact profile: " + compactProfile);
                }
            }
        }

        if ((slimDown != null) && !slimDown.isDefined()) {
            slimDown = null;
        }

        if (slimDown != null) {
            if (!excelsiorJet.isSlimDownSupported()) {
                logger.warn(s("JetApi.NoSlimDown.Warning"));
                slimDown = null;
            } else {
                if (slimDown.detachedBaseURL == null) {
                    throw new JetTaskFailureException(s("JetApi.DetachedBaseURLMandatory.Failure"));
                }

                if (slimDown.detachedPackage == null) {
                    slimDown.detachedPackage = jetProject.artifactName() + ".pkl";
                }

                jetProject.globalOptimizer(true);
            }

        }

        if (diskFootprintReduction != null) {
            DiskFootprintReductionType dfrType = DiskFootprintReductionType.validate(diskFootprintReduction);
            if (!excelsiorJet.isDiskFootprintReductionSupported()) {
                logger.warn(s("JetApi.NoDiskFootprintReduction.Warning"));
                diskFootprintReduction = null;
            } else if (!jetProject.globalOptimizer()) {
                logger.warn(s("JetApi.DiskFootprintReductionForGlobalOnly.Warning"));
                diskFootprintReduction = null;
            } else if (!excelsiorJet.isHighDiskFootprintReductionSupported() &&
                    ((dfrType == DiskFootprintReductionType.HIGH_DISK) ||
                            (dfrType == DiskFootprintReductionType.HIGH_MEMORY))) {
                logger.warn(s("JetApi.NoHighDiskFootprintReduction.Warning", diskFootprintReduction));
                diskFootprintReduction = DiskFootprintReductionType.MEDIUM.toString();
            }
        }

    }
} public enum RuntimeFlavorType {
    CLASSIC,
    DESKTOP,
    SERVER;

    public String toString() {
        return Utils.enumConstantNameToParameter(name());
    }

    public static RuntimeFlavorType validate(String flavor) throws JetTaskFailureException {
        try {
            return RuntimeFlavorType.valueOf(Utils.parameterToEnumConstantName(flavor));
        } catch (Exception e) {
            throw new JetTaskFailureException(s("JetApi.UnknownRuntimeKind.Failure", flavor));
        }
    }
} public class ExcelsiorInstallerConfig {

    private static final String AUTO_DETECT_EULA_ENCODING = "autodetect";
    private static final String UNICODE_EULA_FLAG = "-unicode-eula";
    private static final String EULA_FLAG = "-eula";

    private static final Set<String> VALID_EULA_ENCODING_VALUES = new LinkedHashSet<String>() {{
        add(StandardCharsets.US_ASCII.name());
        add(StandardCharsets.UTF_16LE.name());
        add(AUTO_DETECT_EULA_ENCODING);
    }};

    /**
     * The license agreement file. Used for Excelsior Installer.
     * File containing the end-user license agreement, for Excelsior Installer to display during installation.
     * The file must be a plain text file either in US-ASCII or UTF-16LE encoding.
     * If not set, and the file {@code eula.txt} in {@link JetProject#jetResourcesDir} folder exists,
     * that file is used by convention.
     *
     * @see #eulaEncoding eulaEncoding
     */
    public File eula;

    /**
     * Encoding of the EULA file. Permitted values:
     * <ul>
     *     <li>{@code US-ASCII}</li>
     *     <li>{@code UTF-16LE}</li>
     *     <li>{@code autodetect} (Default value)</li>
     * </ul>
     * If set to {@code autodetect}, the plugin looks for a byte order mark (BOM) in the file specified by {@link #eula}, and:
     * <ul>
     * <li>assumes US-ASCII encoding if no BOM is present,</li>
     * <li>assumes UTF-16LE encoding if the respective BOM ({@code 0xFF 0xFE}) is present, or </li>
     * <li>halts execution with error if some other BOM is present.</li>
     * </ul>
     * @see <a href="https://en.wikipedia.org/wiki/Byte_order_mark">Byte order mark</a>
     * @see #eula eula
     */
    protected String eulaEncoding = AUTO_DETECT_EULA_ENCODING;

    /**
     * (Windows) Excelsior Installer splash screen image in BMP format.
     * If not set, and the file {@code installerSplash.bmp} in {@link JetProject#jetResourcesDir} folder exists,
     * that file is used by convention.
     */
    public File installerSplash;

    /**
     * Forced setup language.
     * <p>
     * Excelsior Installer can display its screens in multiple languages.
     * By default, it selects the most appropriate language based on the locale settings of the target system.
     * Use this parameter to force a specific language instead. Permitted values:
     *  {@code autodetect} (default), {@code english}, {@code french}, {@code german}, {@code japanese}, {@code russian},
     *  {@code polish}, {@code spanish}, {@code italian}, {@code brazilian}.
     * </p>
     * This functionality is available in Excelsior JET 11.3 and above.
     */
    public String language;

    /**
     * Remove all files from the installation folder on uninstall.
     * <p>
     * By default, the uninstaller only removes those files and directories from the installation directory
     * that the original applicaion installer, and possibly update installers, had created.
     * If any files and directories were created by a post-install runnnable, callback DLL,
     * installed and/or third-party applications, the uninstaller will leave them in place and report to the user that
     * it was unable to remove the installation directory due to their presence.
     * </p>
     * This functionality is available in Excelsior JET 11.3 and above.
     */
    public boolean cleanupAfterUninstall;

    /**
     * Excelsior Installer can optionally run one of the executable files included in the package
     * upon successful installation. Use this parameter to specify the executable and its arguments.
     *
     * @see AfterInstallRunnable#target
     * @see AfterInstallRunnable#arguments
     */
    public AfterInstallRunnable afterInstallRunnable = new AfterInstallRunnable();

    /**
     * Packaged files compression level.
     * <p>
     * Excelsior Installer supports three compression levels: {@code fast}, {@code medium} (default),
     * and {@code high} (slow).
     * </p>
     * This functionality is available in Excelsior JET 11.3 and above.
     */
    public String compressionLevel;

    /**
     * Installation directory configuration.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     *
     * @see InstallationDirectory#type
     * @see InstallationDirectory#path
     * @see InstallationDirectory#fixed
     */
    public InstallationDirectory installationDirectory = new InstallationDirectory();

    /**
     * (Windows) Registry key for installation.
     * <p>
     * During installation, Excelsior Installer creates a registry key to store information
     * required for the installation of update packages.
     * The key is located either in the {@code HKEY_LOCAL_MACHINE/SOFTWARE/}
     * or the {@code HKEY_CURRENT_USER/SOFTWARE/} subtree,
     * depending on whether Common or Personal installation type gets selected during installation.
     * By default, the rest of the full name of the key is derived from the values of {@link JetProject#vendor},
     * {@link JetProject#product} and {@link JetProject#version} parameters:
     * {@code company-name/product-name/product-version}. Use this parameter to override that default.
     * </p>
     * This functionality is available in Excelsior JET 11.3 and above.
     */
    public String registryKey;

    /**
     * (Windows) Descriptions of shortcuts that the resulting installer will create.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     * @see Shortcut
     */
    public List<Shortcut> shortcuts = Collections.emptyList();

    /**
     * Default post-install action suppress flag.
     * <p>
     * Upon successful installation, Excelsior Installer can optionally display to the user a list of checkboxes
     * enabling various post-install actions, such as launching the installed application,
     * viewing the readme file, restarting the system, and so on.
     * The default is to add a launch action for each JET-compiled executable in the package with the text
     * "Start executable-name".
     * If you do not want to add the default action, set this parameter to {@code true}.
     * </p>
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     * @see #postInstallCheckboxes
     */
    public boolean noDefaultPostInstallActions;

    /**
     * Post-install actions descriptions.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     * @see #noDefaultPostInstallActions
     */
    public List<PostInstallCheckbox> postInstallCheckboxes = Collections.emptyList();

    /**
     * File associations descriptions.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     * @see FileAssociation
     */
    public List<FileAssociation> fileAssociations = Collections.emptyList();

    /**
     * Install callback dynamic library.
     * If not set, and the file {@code install.dll/libinstall.so} in the {@link JetProject#jetResourcesDir} folder exists,
     * that file is used by convention.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     */
    public File installCallback;

    /**
     * Uninstall callback dynamic library.
     * <p>
     * An uninstall callback dynamic library has to be present on the end user system at the time of uninstall,
     * so you need to specify its location in the project.
     * You may omit {@link PackageFile#path} parameter of the uninstallCallback,
     * if {@link JetProject#packageFilesDir} already contains a library at the specified {@link PackageFile#packagePath}
     * parameter, otherwise the library will be added to the package to the specified {@link PackageFile#packagePath} folder.
     * If the file {@code uninstall.dll/libuninstall.so} in the {@link JetProject#jetResourcesDir} folder exists,
     * that file is used by convention.
     * </p>
     * This functionality is available in Excelsior JET 11.3 and above.
     */
    public PackageFile uninstallCallback = new PackageFile(PackageFileType.FILE);

    /**
     * (Windows) Image to display on the first screen of the installation wizard. Recommended size: 177*314px.
     * If not set, and the file {@code welcomeImage.bmp} in the {@link JetProject#jetResourcesDir} folder exists,
     * that file is used by convention.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     */
    public File welcomeImage;

    /**
     * (Windows) Image to display in the upper-right corner on the second and subsequent screens of the
     * installation wizard. Recommended size: 109*59px.
     * If not set, and the file {@code installerImage.bmp} in the {@link JetProject#jetResourcesDir} folder exists,
     * that file is used by convention.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     */
    public File installerImage;

    /**
     * (Windows) Image to display on the first screen of the uninstall wizard. Recommended size: 177*314px.
     * If not set, and the file {@code uninstallerImage.bmp} in the {@link JetProject#jetResourcesDir} folder exists,
     * that file is used by convention.
     * <p>
     * This functionality is available in Excelsior JET 11.3 and above.
     * </p>
     */
    public File uninstallerImage;

    public void fillDefaults(JetProject project, ExcelsiorJet excelsiorJet) throws JetTaskFailureException {
        //check eula settings
        if (!VALID_EULA_ENCODING_VALUES.contains(eulaEncoding)) {
            throw new JetTaskFailureException(s("JetApi.Package.Eula.UnsupportedEncoding", eulaEncoding));
        }

        eula = checkFileWithDefault(eula, new File(project.jetResourcesDir(), "eula.txt"), "eula");

        installerSplash = checkFileWithDefault(installerSplash,
                new File(project.jetResourcesDir(), "installerSplash.bmp"), "installerSplash");

        if (!excelsiorJet.since11_3()) {
            check11_3ParametersNotSet();
        }

        if (language != null) {
            SetupLanguage.validate(language);
        }

        if (afterInstallRunnable.isDefined()) {
            afterInstallRunnable.validate();
        }

        if (compressionLevel != null) {
            SetupCompressionLevel compression = SetupCompressionLevel.validate(compressionLevel);
            if ((compression != SetupCompressionLevel.FAST) && !excelsiorJet.isAdvancedExcelsiorInstallerFeaturesSupported()) {
                throw new JetTaskFailureException(s("JetApi.ExcelsiorInstaller.UnsupportedCompressionLevel", compressionLevel));
            }
        }

        if (installationDirectory.isDefined()) {
            installationDirectory.validate(excelsiorJet);
        }

        for (Shortcut shortcut: shortcuts) {
            shortcut.validate(excelsiorJet);
        }

        if (!postInstallCheckboxes.isEmpty() && !excelsiorJet.isAdvancedExcelsiorInstallerFeaturesSupported()) {
            throw new JetTaskFailureException(s("JetApi.ExcelsiorInstaller.UnsupportedParameter", "postInstallCheckboxes"));
        } else {
            for (PostInstallCheckbox postInstallCheckbox: postInstallCheckboxes) {
                postInstallCheckbox.validate();
            }
        }

        if (!fileAssociations.isEmpty() && !excelsiorJet.isAdvancedExcelsiorInstallerFeaturesSupported()) {
            throw new JetTaskFailureException(s("JetApi.ExcelsiorInstaller.UnsupportedParameter", "fileAssociations"));
        } else {
            for (FileAssociation fileAssociation: fileAssociations) {
                fileAssociation.validate();
            }
        }

        installCallback = checkFileWithDefault(installCallback,
                new File(project.jetResourcesDir(), excelsiorJet.getTargetOS().mangleDllName("install")), "installCallback");

        if (!uninstallCallback.isDefined()) {
            File uninstall = new File(project.jetResourcesDir(), excelsiorJet.getTargetOS().mangleDllName("uninstall"));
            if (uninstall.exists()) {
                uninstallCallback.path = uninstall;
            }
        }
        uninstallCallback.type = PackageFileType.FILE.toString();
        uninstallCallback.validate("JetApi.ExcelsiorInstaller.FileDoesNotExist", "uninstallCallback");

        welcomeImage = checkBrandingParameter(excelsiorJet, welcomeImage, "welcomeImage",
                new File(project.jetResourcesDir(), "welcomeImage.bmp"));
        installerImage = checkBrandingParameter(excelsiorJet, installerImage, "installerImage",
            new File(project.jetResourcesDir(), "installerImage.bmp"));
        uninstallerImage = checkBrandingParameter(excelsiorJet, uninstallerImage, "uninstallerImage",
            new File(project.jetResourcesDir(), "uninstallerImage.bmp"));
    }

    private void check11_3ParametersNotSet() throws JetTaskFailureException {
        ArrayList<String> parameters = new ArrayList<>();
        if (language != null) {
            parameters.add("language");
        }
        if (afterInstallRunnable.isDefined()) {
           parameters.add("afterInstallRunnable");
        }
        if (compressionLevel != null) {
            parameters.add("compressionLevel");
        }
        if (installationDirectory.isDefined()) {
            parameters.add("installationDirectory");
        }
        if (!shortcuts.isEmpty()) {
            parameters.add("shortcuts");
        }
        if (cleanupAfterUninstall) {
            parameters.add("cleanupAfterUninstall");
        }
        if (registryKey != null) {
            parameters.add("registryKey");
        }
        if (noDefaultPostInstallActions) {
            parameters.add("noDefaultPostInstallActions");
        }
        if (!postInstallCheckboxes.isEmpty()) {
            parameters.add("postInstallCheckboxes");
        }
        if (!fileAssociations.isEmpty()) {
            parameters.add("fileAssociations");
        }
        if (installCallback != null) {
            parameters.add("installCallback");
        }
        if (uninstallCallback.isDefined()) {
            parameters.add("uninstallCallback");
        }
        if (welcomeImage != null) {
            parameters.add("welcomeImage");
        }
        if (installerImage != null) {
            parameters.add("installerImage");
        }
        if (uninstallerImage != null) {
            parameters.add("uninstallerImage");
        }
        if (parameters.size() == 1) {
            throw new JetTaskFailureException(s("JetApi.ExcelsiorInstaller.Since11_3Parameter", parameters.get(0)));
        } else if (parameters.size() > 1) {
            throw new JetTaskFailureException(s("JetApi.ExcelsiorInstaller.Since11_3Parameters", String.join(",", parameters)));
        }
    }

    private static File checkFileWithDefault(File file, File defaultFile, String notExistParam) throws JetTaskFailureException {
        return Utils.checkFileWithDefault(file, defaultFile, "JetApi.ExcelsiorInstaller.FileDoesNotExist", notExistParam);
    }

    private File checkBrandingParameter(ExcelsiorJet excelsiorJet, File parValue, String parName, File defaultValue) throws JetTaskFailureException {
        if (!excelsiorJet.isAdvancedExcelsiorInstallerFeaturesSupported()) {
            if (parValue != null) {
                throw new JetTaskFailureException(s("JetApi.ExcelsiorInstaller.UnsupportedParameter", parName));
            } else {
                return null;
            }
        }
        return checkFileWithDefault(parValue, defaultValue, parName);
    }

    public String eulaFlag() throws JetTaskFailureException {
        String actualEncoding;
        try {
            actualEncoding = detectEncoding(eula);
        } catch (IOException e) {
            throw new JetTaskFailureException(e.getMessage(), e);
        }

        if (!AUTO_DETECT_EULA_ENCODING.equals(eulaEncoding)) {
            if (!actualEncoding.equals(eulaEncoding)) {
                throw new JetTaskFailureException(s("JetApi.Package.Eula.EncodingDoesNotMatchActual", actualEncoding, eulaEncoding));
            }
        }

        if (StandardCharsets.UTF_16LE.name().equals(actualEncoding)) {
            return UNICODE_EULA_FLAG;
        } else if (StandardCharsets.US_ASCII.name().equals(actualEncoding)) {
            return EULA_FLAG;
        } else {
            throw new JetTaskFailureException(s("JetApi.Package.Eula.UnsupportedEncoding", eulaEncoding));
        }
    }
} public class WindowsServiceConfig {

    /**
     * The <em>system</em> name of the service,
     * used to install, remove and otherwise manage the service.
     * It can also be used to recognize messages from this service in the system event log.
     * The system name is set during the creation of the service executable.
     * It is displayed for reference so you cannot edit it.
     * <p>
     * By default, {@link JetProject#outputName} is used for the name.
     * </p>
     */
    public String name;

    /**
     * The <em>descriptive</em> name of the service.
     * It is shown in the Event Viewer system tool and in the Services applet of the Windows Control Panel.
     * <p>
     * By default, the value of {@link #name} is re-used as the display name.
     * </p>
     */
    public String displayName;

    /**
     * The user description of the service. It must not exceed 1,000 characters.
     */
    public String description;

    /**
     * The command-line arguments passed to the service upon startup.
     */
    public String[] arguments;

    /**
     * Specifies the account to be used by the service. Valid values are: "local-system-account" (default), "user-account".
     * <p>
     * {@code local-system-account} - run the service under the built-in system account.
     * </p>
     * <p>
     * {@code user-account} - run the service under a user account.
     *                        When installing the package, the user will be prompted for an account name
     *                        and password necessary to run the service.
     * </p>
     */
    public String logOnType;

    /**
     * Specifies if the service needs to interact with the system desktop,
     * e.g. open/close other windows, etc. This option is only available if {@link #logOnType}
     * is set to {@code local-system-account}.
     */
    public boolean allowDesktopInteraction;

    /**
     * Specify how to start the service. Valid values are "automatic" (default), "manual", and "disabled".
     * <p>
     * {@code automatic} - specifies that the service should start automatically when the system starts.
     * </p>
     * <p>
     * {@code manual} - specifies that a user or a dependent service can start the service.
     *                  Services with Manual startup type do not start automatically when the system starts.
     * </p>
     * <p>
     * {@code disabled} - prevents the service from being started by the system, a user, or any dependent service.
     * </p>
     */
    public String startupType;

    /**
     * Specifies if the service should be started immediately after installation.
     *
     * Available only for {@link PackagingType#EXCELSIOR_INSTALLER} packaging type.
     *
     */
    public boolean startServiceAfterInstall = true;

    /**
     * List of other service names on which the service depends.
     */
    public String[] dependencies;

    public void fillDefaults(JetProject jetProject) throws JetTaskFailureException {
        if (Utils.isEmpty(name)) {
            name = jetProject.outputName();
        }

        if (Utils.isEmpty(displayName)) {
            displayName = jetProject.appType() == ApplicationType.TOMCAT ? "Apache Tomcat": name;
        }

        if (description == null) {
            description = jetProject.appType() == ApplicationType.TOMCAT ?
                    "Apache Tomcat Server - http://tomcat.apache.org/": displayName;
        }

        if (logOnType == null) {
            logOnType = LogOnType.LOCAL_SYSTEM_ACCOUNT.toString();
        } else {
            LogOnType.validate(logOnType);
        }
        if (startupType == null) {
            startupType = StartupType.AUTOMATIC.toString();
        } else {
            StartupType.validate(startupType);
        }

        if (allowDesktopInteraction && (LogOnType.fromString(logOnType) != LogOnType.LOCAL_SYSTEM_ACCOUNT)) {
            throw new JetTaskFailureException(s("JetApi.IntractiveSeviceForNonLocalSystem.Failure", startupType));
        }
    }

    public LogOnType getLogOnType() {
        return LogOnType.fromString(logOnType);
    }

    public StartupType getStartupType() {
        return StartupType.fromString(startupType);
    }
}
import com.excelsiorjet.api.ExcelsiorJet;
import com.excelsiorjet.api.JetEdition;
import com.excelsiorjet.api.log.StdOutLog;
import com.excelsiorjet.api.platform.OS;
import com.excelsiorjet.api.tasks.config.*;
import com.excelsiorjet.api.tasks.config.ApplicationType;
import com.excelsiorjet.api.tasks.config.compiler.ExecProfilesConfig;
import com.excelsiorjet.api.tasks.config.compiler.WindowsVersionInfoConfig;
import com.excelsiorjet.api.tasks.config.runtime.RuntimeConfig;
import com.excelsiorjet.api.tasks.config.runtime.RuntimeFlavorType;
import com.excelsiorjet.api.tasks.config.excelsiorinstaller.ExcelsiorInstallerConfig;
import com.excelsiorjet.api.tasks.config.windowsservice.WindowsServiceConfig;
import org.mockito.Mockito;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.ResourceBundle;
import static java.util.Collections.emptyList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
package com.excelsiorjet.api.tasks;




public class Tests {

    static final Path testBaseDir = Paths.get(System.getProperty("java.io.tmpdir"), "excelsior-jet-api-test");
    private static final Path mavenLocalDir = testBaseDir.resolve(".m2");
    static final Path projectDir = testBaseDir.resolve("prj");
    private static final Path buildDir = projectDir.resolve("build");
    static final Path mainJar = buildDir.resolve("test.jar");
    private static final Path jetDir = buildDir.resolve("jet");
    static final Path jetBuildDir = jetDir.resolve("build");
    static final Path jetAppDir = jetDir.resolve("app");
    static final Path externalJarRel = Paths.get("lib", "external.jar");
    static final Path externalJarAbs = projectDir.resolve(externalJarRel);

    public static File dirSpy(String path) {
        File spy = Mockito.spy(new File(path));
        Mockito.when(spy.isFile()).thenReturn(false);
        Mockito.when(spy.isDirectory()).thenReturn(true);
        Mockito.when(spy.exists()).thenReturn(true);
        return spy;
    }

    static File dirSpy(Path path) {
        return dirSpy(path.toString());
    }

    public static File fileSpy(String path) {
        File spy = Mockito.spy(new File(path));
        Mockito.when(spy.isFile()).thenReturn(true);
        Mockito.when(spy.isDirectory()).thenReturn(false);
        Mockito.when(spy.exists()).thenReturn(true);
        return spy;
    }

    static File fileSpy(Path path) {
        return fileSpy(path.toString());
    }

    public static File fileSpy(String path, long modifyTime) {
        File spy = fileSpy(path);
        Mockito.when(spy.lastModified()).thenReturn(modifyTime);
        return spy;
    }

    static File mavenDepSpy(String depName) {
        return fileSpy(mavenLocalDir.resolve(depName).toString());
    }

    public static JetProject testProject(ApplicationType appType) throws JetTaskFailureException {
        JetProject.configureEnvironment(new StdOutLog(), ResourceBundle.getBundle("Strings"));
        JetProject project = new JetProject("test", "test", "prjGroup", "0.1", appType, buildDir.toFile(), new File("/jr")).
                inlineExpansion("tiny-methods-only").
                runArgs(new String[0]).
                addWindowsVersionInfo(false).
                stackAllocation(true).
                projectDependencies(emptyList()).
                dependencies(emptyList()).
                mainClass("HelloWorld").
                jetBuildDir(jetBuildDir.toFile()).
                jetAppDir(jetAppDir.toFile()).
                packageFiles(Collections.emptyList()).
                excelsiorInstallerConfiguration(new ExcelsiorInstallerConfig()).
                windowsServiceConfiguration(new WindowsServiceConfig()).
                windowsVersionInfoConfiguration(new WindowsVersionInfoConfig()).
                runtimeConfiguration(new RuntimeConfig()).