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.
/*
* 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