--- /dev/null
+/*
+ * SonarQube Scanner
+ * Copyright (C) 2011-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarsource.scanner.cli;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PropertyResolver {
+ private static final Pattern placeholderPattern = Pattern.compile("\\$\\{([\\w\\.]+)\\}|\\$([\\w\\.]+)");
+ private final Properties props;
+ private final Properties resolved;
+ private final List<String> queue;
+ private Map<String, String> env;
+
+ public PropertyResolver(Properties props, Map<String, String> env) {
+ this.props = props;
+ this.env = env;
+ this.resolved = new Properties();
+ this.queue = new LinkedList<>();
+ }
+
+ public Properties resolve() {
+ for (Map.Entry<Object, Object> e : props.entrySet()) {
+ if (resolved.containsKey(e.getKey())) {
+ continue;
+ }
+ resolveProperty((String) e.getKey());
+ }
+
+ return resolved;
+ }
+
+ private String getValue(String key) {
+ String propValue;
+
+ if (key.startsWith("env.")) {
+ String envKey = key.substring(4);
+ propValue = env.get(envKey);
+ } else {
+ propValue = props.getProperty(key);
+ }
+
+ return propValue != null ? propValue : "";
+ }
+
+ private String resolveProperty(String propKey) {
+ String propValue = getValue(propKey);
+ if (propValue.isEmpty()) {
+ return propValue;
+ }
+
+ Matcher m = placeholderPattern.matcher(propValue);
+ StringBuffer sb = new StringBuffer();
+
+ while (m.find()) {
+ String varName = (null == m.group(1)) ? m.group(2) : m.group(1);
+ if (queue.contains(varName)) {
+ throw new IllegalArgumentException("Found a loop resolving place holders in properties, for variable: " + varName);
+ }
+
+ String placeholderValue = resolveVar(varName);
+ m.appendReplacement(sb, Matcher.quoteReplacement(placeholderValue));
+ }
+ m.appendTail(sb);
+
+ String resolvedPropValue = sb.toString();
+ resolved.setProperty(propKey, resolvedPropValue);
+ return resolvedPropValue;
+ }
+
+ private String resolveVar(String varName) {
+ String placeholderValue;
+ if (resolved.containsKey(varName)) {
+ placeholderValue = resolved.getProperty(varName);
+ } else {
+ queue.add(varName);
+ placeholderValue = resolveProperty(varName);
+ queue.remove(varName);
+ }
+ return placeholderValue;
+
+ }
+}
--- /dev/null
+/*
+ * SonarQube Scanner
+ * Copyright (C) 2011-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarsource.scanner.cli;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class PropertyResolverTest {
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void resolve_properties() {
+ Properties map = new Properties();
+ Map<String, String> env = new HashMap<>();
+
+ // resolve
+ map.put("A", "value a");
+ map.put("B", "value b");
+ map.put("C", "${A} ${B} ${nonexisting}");
+
+ PropertyResolver resolver = new PropertyResolver(map, env);
+ Properties resolved = resolver.resolve();
+ assertThat(resolved.get("A")).isEqualTo("value a");
+ assertThat(resolved.get("B")).isEqualTo("value b");
+ assertThat(resolved.get("C")).isEqualTo("value a value b ");
+
+ map.clear();
+ map.put("sonar.login", "admin");
+ map.put("sonar.password", "${sonar.login}");
+
+ resolver = new PropertyResolver(map, env);
+ resolved = resolver.resolve();
+ assertThat(resolved.get("sonar.password")).isEqualTo("admin");
+ }
+
+ @Test
+ public void use_env() {
+ Properties map = new Properties();
+ Map<String, String> env = new HashMap<>();
+
+ // resolve
+ map.put("A", "invalid");
+ map.put("B", "value b");
+ map.put("C", "${env.A} ${B} ${nonexisting}");
+ env.put("A", "value a");
+
+ PropertyResolver resolver = new PropertyResolver(map, env);
+ Properties resolved = resolver.resolve();
+ assertThat(resolved.get("A")).isEqualTo("invalid");
+ assertThat(resolved.get("B")).isEqualTo("value b");
+ assertThat(resolved.get("C")).isEqualTo("value a value b ");
+ }
+
+ @Test
+ public void resolve_recursively() {
+ Properties map = new Properties();
+ Map<String, String> env = new HashMap<>();
+ map.put("A", "value a");
+ map.put("B", "${A}");
+ map.put("C", "${A} ${B}");
+
+ PropertyResolver resolver = new PropertyResolver(map, env);
+ Properties resolved = resolver.resolve();
+ assertThat(resolved.get("A")).isEqualTo("value a");
+ assertThat(resolved.get("B")).isEqualTo("value a");
+ assertThat(resolved.get("C")).isEqualTo("value a value a");
+ }
+
+ @Test
+ public void dont_resolve_nested() {
+ Properties map = new Properties();
+ Map<String, String> env = new HashMap<>();
+ map.put("A", "value a");
+ map.put("B", "value b");
+ map.put("C", "${A ${B}}");
+
+ PropertyResolver resolver = new PropertyResolver(map, env);
+ Properties resolved = resolver.resolve();
+ assertThat(resolved.get("A")).isEqualTo("value a");
+ assertThat(resolved.get("B")).isEqualTo("value b");
+ assertThat(resolved.get("C")).isEqualTo("${A value b}");
+ }
+
+ @Test
+ public void fail_loop_properties_resolution() {
+ Properties map = new Properties();
+ Map<String, String> env = new HashMap<>();
+
+ // resolve
+ map.put("A", "${B}");
+ map.put("B", "${A}");
+
+ exception.expect(IllegalArgumentException.class);
+ exception.expectMessage("variable: B");
+ PropertyResolver resolver = new PropertyResolver(map, env);
+ resolver.resolve();
+ }
+}