All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zenjava.javafx.maven.plugin.Workarounds Maven / Gradle / Ivy

Go to download

The JavaFX Maven Plugin provides a way to to assemble distributable bundles for JavaFX applications from within Maven. It provides a wrapper around the JavaFX packaging tools which are provided as part of the JavaFX installation.

There is a newer version: 8.8.3
Show newest version
/*
 * Copyright 2012 Daniel Zwolenski.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zenjava.javafx.maven.plugin;

import com.oracle.tools.packager.Bundler;
import com.oracle.tools.packager.IOUtils;
import com.oracle.tools.packager.RelativeFileSet;
import com.oracle.tools.packager.StandardBundlerParam;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.apache.maven.plugin.logging.Log;

/**
 *
 * @author Danny Althoff
 */
public class Workarounds {

    private static final String JNLP_JAR_PATTERN = "(.*)href=(\".*?\")(.*)size=(\".*?\")(.*)";

    private static final String CONFIG_FILE_EXTENSION = ".cfg";

    private Log logger;
    private File nativeOutputDir;

    public Workarounds(File nativeOutputDir, Log logger) {
        this.logger = logger;
        this.nativeOutputDir = nativeOutputDir;
    }

    public Log getLog() {
        return logger;
    }

    public boolean isWorkaroundForBug124Needed() {
        return JavaDetectionTools.IS_JAVA_8 && JavaDetectionTools.isAtLeastOracleJavaUpdateVersion(40) || JavaDetectionTools.IS_JAVA_9;
    }

    public boolean isWorkaroundForBug167Needed() {
        // this has been fixed and made available since 1.8.0u92:
        // http://www.oracle.com/technetwork/java/javase/2col/8u92-bugfixes-2949473.html
        return JavaDetectionTools.IS_JAVA_8 && JavaDetectionTools.isAtLeastOracleJavaUpdateVersion(60) && !JavaDetectionTools.isAtLeastOracleJavaUpdateVersion(92);
    }

    public boolean isWorkaroundForBug182Needed() {
        // jnlp-bundler uses RelativeFileSet, and generates system-dependent dividers (\ on windows, / on others)
        return File.separator.equals("\\");
    }

    public boolean isWorkaroundForBug185Needed(Map params) {
        return params.containsKey("jnlp.allPermisions") && Boolean.parseBoolean(String.valueOf(params.get("jnlp.allPermisions")));
    }

    public boolean isWorkaroundForBug205Needed() {
        return (JavaDetectionTools.IS_JAVA_8 && JavaDetectionTools.isAtLeastOracleJavaUpdateVersion(40)) || JavaDetectionTools.IS_JAVA_9;
    }

    protected void applyNativeLauncherWorkaround(String appName) {
        // check appName containing any dots
        boolean needsWorkaround = appName.contains(".");
        if( !needsWorkaround ){
            return;
        }
        // rename .cfg-file (makes it able to create running applications again, even within installer)
        String newConfigFileName = appName.substring(0, appName.lastIndexOf("."));
        Path appPath = nativeOutputDir.toPath().resolve(appName).resolve("app");
        Path oldConfigFile = appPath.resolve(appName + CONFIG_FILE_EXTENSION);
        try{
            Files.move(oldConfigFile, appPath.resolve(newConfigFileName + CONFIG_FILE_EXTENSION), StandardCopyOption.ATOMIC_MOVE);
        } catch(IOException ex){
            getLog().warn("Couldn't rename configfile. Please see issue #124 of the javafx-maven-plugin for further details.", ex);
        }
    }

    protected Map getFileSizes(List files) {
        final Map fileSizes = new HashMap<>();
        files.stream().forEach(relativeFilePath -> {
            File file = new File(nativeOutputDir, relativeFilePath);
            // add the size for each file
            fileSizes.put(relativeFilePath, file.length());
        });
        return fileSizes;
    }

    public void fixFileSizesWithinGeneratedJNLPFiles() {
        // after signing, we have to adjust sizes, because they have changed (since they are modified with the signature)
        List jarFiles = getJARFilesFromJNLPFiles();
        Map newFileSizes = getFileSizes(jarFiles);
        List generatedJNLPFiles = getGeneratedJNLPFiles();
        Pattern pattern = Pattern.compile(JNLP_JAR_PATTERN);
        generatedJNLPFiles.forEach(file -> {
            try{
                List allLines = Files.readAllLines(file.toPath());
                List newLines = new ArrayList<>();
                allLines.stream().forEach(line -> {
                    if( line.matches(JNLP_JAR_PATTERN) ){
                        // get jar-file
                        Matcher matcher = pattern.matcher(line);
                        matcher.find();
                        String rawJarName = matcher.group(2);
                        String jarName = rawJarName.substring(1, rawJarName.length() - 1);
                        if( newFileSizes.containsKey(jarName) ){
                            // replace old size with new one
                            newLines.add(line.replace(matcher.group(4), "\"" + newFileSizes.get(jarName) + "\""));
                        } else {
                            newLines.add(line);
                        }
                    } else {
                        newLines.add(line);
                    }
                });
                Files.write(file.toPath(), newLines, StandardOpenOption.TRUNCATE_EXISTING);
            } catch(IOException ignored){
                // NO-OP
            }
        });
    }

    public List getGeneratedJNLPFiles() {
        List generatedFiles = new ArrayList<>();

        // try-ressource, because walking on files is lazy, resulting in file-handler left open otherwise
        try(Stream walkstream = Files.walk(nativeOutputDir.toPath())){
            walkstream.forEach(fileEntry -> {
                File possibleJNLPFile = fileEntry.toFile();
                String fileName = possibleJNLPFile.getName();
                if( fileName.endsWith(".jnlp") ){
                    generatedFiles.add(possibleJNLPFile);
                }
            });
        } catch(IOException ignored){
            // NO-OP
        }

        return generatedFiles;
    }

    public List getJARFilesFromJNLPFiles() {
        List jarFiles = new ArrayList<>();
        getGeneratedJNLPFiles().stream().map(jnlpFile -> jnlpFile.toPath()).forEach(jnlpPath -> {
            try{
                List allLines = Files.readAllLines(jnlpPath);
                allLines.stream().filter(line -> line.trim().startsWith(" generatedJNLPFiles = getGeneratedJNLPFiles();
        Pattern pattern = Pattern.compile(JNLP_JAR_PATTERN);
        generatedJNLPFiles.forEach(file -> {
            try{
                List allLines = Files.readAllLines(file.toPath());
                List newLines = new ArrayList<>();
                allLines.stream().forEach(line -> {
                    if( line.matches(JNLP_JAR_PATTERN) ){
                        // get jar-file
                        Matcher matcher = pattern.matcher(line);
                        matcher.find();
                        String rawJarName = matcher.group(2);
                        // replace \ with /
                        newLines.add(line.replace(rawJarName, rawJarName.replaceAll("\\\\", "\\/")));
                    } else {
                        newLines.add(line);
                    }
                });
                Files.write(file.toPath(), newLines, StandardOpenOption.TRUNCATE_EXISTING);
            } catch(IOException ignored){
                // NO-OP
            }
        });
    }

    public void applyWorkaround124(String appName, List secondaryLaunchers) {
        // apply on main launcher
        applyNativeLauncherWorkaround(appName);

        // check on secondary launchers too
        if( secondaryLaunchers != null && !secondaryLaunchers.isEmpty() ){
            secondaryLaunchers.stream().map(launcher -> {
                return launcher.getAppName();
            }).filter(launcherAppName -> {
                // check appName containing any dots (which is the bug)
                return launcherAppName.contains(".");
            }).forEach(launcherAppname -> {
                applyNativeLauncherWorkaround(launcherAppname);
            });
        }
    }

    public void applyWorkaround185(boolean skipSizeRecalculationForJNLP185) {
        if( !skipSizeRecalculationForJNLP185 ){
            getLog().info("Fixing sizes of JAR files within JNLP-files");
            fixFileSizesWithinGeneratedJNLPFiles();
        } else {
            getLog().info("Skipped fixing sizes of JAR files within JNLP-files");
        }
    }

    public void applyWorkaround167(Map params) {
        if( params.containsKey("runtime") ){
            getLog().info("Applying workaround for oracle-jdk-bug since 1.8.0u60 regarding cfg-file-format");
            // the problem is com.oracle.tools.packager.windows.WinAppBundler within createLauncherForEntryPoint-Method
            // it does NOT respect runtime-setting while calling "writeCfgFile"-method of com.oracle.tools.packager.AbstractImageBundler
            // since newer java versions (they added possability to have INI-file-format of generated cfg-file, since 1.8.0_60).
            // Because we want to have backward-compatibility within java 8, we will use parameter-name as hardcoded string!
            // Our workaround: use prop-file-format
            params.put("launcher-cfg-format", "prop");
        }
    }

    /**
     * Get generated, fixed cfg-files and push them to app-resources-list.
     *
     *
     * @param appName
     * @param secondaryLaunchers
     * @param params
     */
    public void applyWorkaround205(String appName, List secondaryLaunchers, Map params) {
        // to workaround, we are gathering the fixed versions of the previous executed "app-bundler"
        // and assume they all are existing
        Set filenameFixedConfigFiles = new HashSet<>();

        // get cfg-file of main native launcher
        Path appPath = nativeOutputDir.toPath().resolve(appName).resolve("app").toAbsolutePath();
        if( appName.contains(".") ){
            String newConfigFileName = appName.substring(0, appName.lastIndexOf("."));
            File mainAppNameCfgFile = appPath.resolve(newConfigFileName + CONFIG_FILE_EXTENSION).toFile();
            if( mainAppNameCfgFile.exists() ){
                getLog().info("Found main native application configuration file (" + mainAppNameCfgFile.toString() + ").");
            }
            filenameFixedConfigFiles.add(mainAppNameCfgFile);
        }

        // when having secondary native launchers, we need their cfg-files too
        Optional.ofNullable(secondaryLaunchers).ifPresent(launchers -> {
            launchers.stream().map(launcher -> {
                return launcher.getAppName();
            }).forEach(secondaryLauncherAppName -> {
                if( secondaryLauncherAppName.contains(".") ){
                    String newSecondaryLauncherConfigFileName = secondaryLauncherAppName.substring(0, secondaryLauncherAppName.lastIndexOf("."));
                    filenameFixedConfigFiles.add(appPath.resolve(newSecondaryLauncherConfigFileName + CONFIG_FILE_EXTENSION).toFile());
                }
            });
        });

        if( filenameFixedConfigFiles.isEmpty() ){
            // it wasn't required to apply this workaround
            getLog().info("No workaround for native launcher issue 205 needed. Continuing.");
            return;
        }
        getLog().info("Applying workaround for native launcher issue 205 by modifying application resources.");

        // since 1.8.0_60 there exists some APP_RESOURCES_LIST, which contains multiple RelativeFileSet-instances
        // this is the more easy way ;)
        List appResourcesList = new ArrayList<>();
        RelativeFileSet appResources = StandardBundlerParam.APP_RESOURCES.fetchFrom(params);
        // original application resources
        appResourcesList.add(appResources);
        // additional filename-fixed cfg-files
        appResourcesList.add(new RelativeFileSet(appPath.toFile(), filenameFixedConfigFiles));

        // special workaround when having some jdk before update 60
        if( JavaDetectionTools.IS_JAVA_8 && !JavaDetectionTools.isAtLeastOracleJavaUpdateVersion(60) ){
            try{
                // pre-update60 did not contain any list of RelativeFileSets, which requires to rework APP_RESOURCES :/
                Path tempResourcesDirectory = Files.createTempDirectory("jfxmp-workaround205-").toAbsolutePath();
                File tempResourcesDirAsFile = tempResourcesDirectory.toFile();
                getLog().info("Modifying application resources for native launcher issue 205 by copying into temporary folder (" + tempResourcesDirAsFile.toString() + ").");
                for( RelativeFileSet sources : appResourcesList ){
                    File baseDir = sources.getBaseDirectory();
                    for( String fname : appResources.getIncludedFiles() ){
                        IOUtils.copyFile(new File(baseDir, fname), new File(tempResourcesDirAsFile, fname));
                    }
                }

                // might not work for gradle, but maven does not hold up any JVM ;)
                // might rework this later into cleanup-phase
                tempResourcesDirAsFile.deleteOnExit();

                // generate new RelativeFileSet with fixed cfg-file
                Set fixedResourceFiles = new HashSet<>();
                try(Stream walkstream = Files.walk(tempResourcesDirectory)){
                    walkstream.
                            map(p -> p.toFile())
                            .filter(File::isFile)
                            .filter(File::canRead)
                            .forEach(f -> {
                                getLog().info(String.format("Add %s file to application resources.", f));
                                fixedResourceFiles.add(f);
                            });
                } catch(IOException ignored){
                    // NO-OP
                }
                params.put(StandardBundlerParam.APP_RESOURCES.getID(), new RelativeFileSet(tempResourcesDirAsFile, fixedResourceFiles));
            } catch(IOException ex){
                getLog().warn(ex);
            }
            return;
        }
        /*
         * Backward-compatibility note:
         * When using JDK 1.8.0u51 on travis-ci it would results into "cannot find symbol: variable APP_RESOURCES_LIST"!
         *
         * To solve this, we are using some hard-coded map-key :/ (please no hacky workaround via reflections .. urgh)
         */
        params.put(StandardBundlerParam.APP_RESOURCES.getID() + "List", appResourcesList);
    }

    public boolean isWorkaroundForNativeMacBundlerNeeded(File additionalBundlerResources) {
        boolean isMac = System.getProperty("os.name").toLowerCase().contains("os x");
        boolean hasBundlerResources = additionalBundlerResources != null && additionalBundlerResources.isDirectory() && additionalBundlerResources.exists();

        return isMac && hasBundlerResources;
    }

    public Bundler applyWorkaroundForNativeMacBundler(final Bundler b, String currentRunningBundlerID, Map params, File additionalBundlerResources) {
        String workaroundForNativeMacBundlerDoneMarker = "WorkaroundForNativeMacBundler.done";
        if( "mac.app".equals(currentRunningBundlerID) && !params.containsKey(workaroundForNativeMacBundlerDoneMarker) ){
            // 1) replace current running bundler with our own implementation
            Bundler specialMacBundler = new MacAppBundlerWithAdditionalResources();

            // 2) replace other bundlers using mac.app-bundler inside
            getLog().info("Setting replacement of the 'mac.app'-bundler.");
            params.put("mac.app.bundler", specialMacBundler);
            params.put(workaroundForNativeMacBundlerDoneMarker, true);
            Path specificFolder = additionalBundlerResources.toPath().resolve("mac.app");
            // check if there is a special folder, otherwise put all stuff here
            if( Files.exists(specificFolder) && Files.isDirectory(specificFolder) ){
                getLog().info("Using special 'mac.app' bundler-folder.");
                params.put(MacAppBundlerWithAdditionalResources.ADDITIONAL_BUNDLER_RESOURCES.getID(), specificFolder.toAbsolutePath().toFile());
            } else {
                params.put(MacAppBundlerWithAdditionalResources.ADDITIONAL_BUNDLER_RESOURCES.getID(), additionalBundlerResources);
            }

            return specialMacBundler;
        }
        return b;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy