/* * Copyright 2015 the original author or authors. * * 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 org.gradle.api.internal.plugins; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import org.apache.commons.lang.StringUtils; import org.gradle.api.Transformer; import org.gradle.jvm.application.scripts.JavaAppStartScriptGenerationDetails; import org.gradle.util.CollectionUtils; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class StartScriptTemplateBindingFactory implements Transformer<Map<String, String>, JavaAppStartScriptGenerationDetails> { private final boolean windows; private StartScriptTemplateBindingFactory(boolean windows) { this.windows = windows; } public static StartScriptTemplateBindingFactory windows() { return new StartScriptTemplateBindingFactory(true); } public static StartScriptTemplateBindingFactory unix() { return new StartScriptTemplateBindingFactory(false); } @Override public Map<String, String> transform(JavaAppStartScriptGenerationDetails details) { Map<String, String> binding = new HashMap<String, String>(); binding.put(ScriptBindingParameter.APP_NAME.getKey(), details.getApplicationName()); binding.put(ScriptBindingParameter.OPTS_ENV_VAR.getKey(), details.getOptsEnvironmentVar()); binding.put(ScriptBindingParameter.EXIT_ENV_VAR.getKey(), details.getExitEnvironmentVar()); binding.put(ScriptBindingParameter.MAIN_CLASSNAME.getKey(), details.getMainClassName()); binding.put(ScriptBindingParameter.DEFAULT_JVM_OPTS.getKey(), createJoinedDefaultJvmOpts(details.getDefaultJvmOpts())); binding.put(ScriptBindingParameter.APP_NAME_SYS_PROP.getKey(), details.getAppNameSystemProperty()); binding.put(ScriptBindingParameter.APP_HOME_REL_PATH.getKey(), createJoinedAppHomeRelativePath(details.getScriptRelPath())); binding.put(ScriptBindingParameter.CLASSPATH.getKey(), createJoinedClasspath(details.getClasspath())); return binding; } private String createJoinedClasspath(Iterable<String> classpath) { if (windows) { return Joiner.on(";").join(Iterables.transform(classpath, new Function<String, String>() { public String apply(String input) { return "%APP_HOME%\\" + input.replace("/", "\\"); } })); } else { return Joiner.on(":").join(Iterables.transform(classpath, new Function<String, String>() { public String apply(String input) { return "$APP_HOME/" + input.replace("\\", "/"); } })); } } private String createJoinedDefaultJvmOpts(Iterable<String> defaultJvmOpts) { if (windows) { if (defaultJvmOpts == null) { return ""; } Iterable<String> quotedDefaultJvmOpts = Iterables.transform(CollectionUtils.toStringList(defaultJvmOpts), new Function<String, String>() { public String apply(String jvmOpt) { return "\"" + escapeWindowsJvmOpt(jvmOpt) + "\""; } }); Joiner spaceJoiner = Joiner.on(" "); return spaceJoiner.join(quotedDefaultJvmOpts); } else { if (defaultJvmOpts == null) { return ""; } Iterable<String> quotedDefaultJvmOpts = Iterables.transform(CollectionUtils.toStringList(defaultJvmOpts), new Function<String, String>() { public String apply(String jvmOpt) { //quote ', ", \, $. Probably not perfect. TODO: identify non-working cases, fail-fast on them jvmOpt = jvmOpt.replace("\\", "\\\\"); jvmOpt = jvmOpt.replace("\"", "\\\""); jvmOpt = jvmOpt.replace("'", "'\"'\"'"); jvmOpt = jvmOpt.replace("`", "'\"`\"'"); jvmOpt = jvmOpt.replace("$", "\\$"); return "\"" + jvmOpt + "\""; } }); //put the whole arguments string in single quotes, unless defaultJvmOpts was empty, // in which case we output "" to stay compatible with existing builds that scan the script for it Joiner spaceJoiner = Joiner.on(" "); if (Iterables.size(quotedDefaultJvmOpts) > 0) { return "'" + spaceJoiner.join(quotedDefaultJvmOpts) + "'"; } return "\"\""; } } private String escapeWindowsJvmOpt(String jvmOpts) { boolean wasOnBackslash = false; StringBuilder escapedJvmOpt = new StringBuilder(); CharacterIterator it = new StringCharacterIterator(jvmOpts); //argument quoting: // - " must be encoded as \" // - % must be encoded as %% // - pathological case: \" must be encoded as \\\", but other than that, \ MUST NOT be quoted // - other characters (including ') will not be quoted // - use a state machine rather than regexps for (char ch = it.first(); ch != CharacterIterator.DONE; ch = it.next()) { String repl = Character.toString(ch); if (ch == '%') { repl = "%%"; } else if (ch == '"') { repl = (wasOnBackslash ? '\\' : "") + "\\\""; } wasOnBackslash = ch == '\\'; escapedJvmOpt.append(repl); } return escapedJvmOpt.toString(); } private static enum ScriptBindingParameter { APP_NAME("applicationName"), OPTS_ENV_VAR("optsEnvironmentVar"), EXIT_ENV_VAR("exitEnvironmentVar"), MAIN_CLASSNAME("mainClassName"), DEFAULT_JVM_OPTS("defaultJvmOpts"), APP_NAME_SYS_PROP("appNameSystemProperty"), APP_HOME_REL_PATH("appHomeRelativePath"), CLASSPATH("classpath"); private final String key; private ScriptBindingParameter(String key) { this.key = key; } public String getKey() { return key; } } String createJoinedAppHomeRelativePath(String scriptRelPath) { int depth = StringUtils.countMatches(scriptRelPath, "/"); if (depth == 0) { return ""; } List<String> appHomeRelativePath = new ArrayList<String>(depth); for (int i = 0; i < depth; i++) { appHomeRelativePath.add(".."); } return Joiner.on(windows ? "\\" : "/").join(appHomeRelativePath); } }