You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2015/05/21 12:04:53 UTC
[2/2] jclouds git commit: JCLOUDS-897: Remove the Rocoto dependency
JCLOUDS-897: Remove the Rocoto dependency
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/7053a787
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/7053a787
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/7053a787
Branch: refs/heads/master
Commit: 7053a7870da15c3996ca0fd6567f398ac9a5dfb0
Parents: 68a429f
Author: Ignasi Barrera <na...@apache.org>
Authored: Fri May 1 02:50:12 2015 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu May 21 00:26:57 2015 +0200
----------------------------------------------------------------------
.gitignore | 2 +
.../swift/internal/BasePayloadTest.java | 16 ++-
core/pom.xml | 10 +-
.../main/java/org/jclouds/ContextBuilder.java | 23 ++--
.../config/BindPropertiesToExpandedValues.java | 62 -----------
.../org/jclouds/functions/ExpandProperties.java | 110 +++++++++++++++++++
.../java/org/jclouds/ContextBuilderTest.java | 37 +++++--
.../apis/JcloudsTestBlobStoreApiMetadata.java | 2 +-
.../BindPropertiesToExpandedValuesTest.java | 45 --------
.../jclouds/functions/ExpandPropertiesTest.java | 74 +++++++++++++
10 files changed, 239 insertions(+), 142 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 3242d22..8c20bff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@ TAGS
atlassian-ide-plugin.xml
.DS_Store
.java-version
+.factorypath
+.apt_generated
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/BasePayloadTest.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/BasePayloadTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/BasePayloadTest.java
index 8694f5a..8142397 100644
--- a/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/BasePayloadTest.java
+++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/BasePayloadTest.java
@@ -20,27 +20,31 @@ import static org.jclouds.reflect.Reflection2.method;
import java.net.URI;
import java.util.List;
+import java.util.Properties;
+import org.jclouds.functions.ExpandProperties;
import org.jclouds.json.config.GsonModule;
import org.jclouds.json.config.GsonModule.DateAdapter;
import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
import org.jclouds.openstack.swift.SwiftApiMetadata;
import org.jclouds.reflect.Invocation;
import org.jclouds.rest.internal.GeneratedHttpRequest;
-import org.nnsoft.guice.rocoto.Rocoto;
-import org.nnsoft.guice.rocoto.configuration.ConfigurationModule;
import com.google.common.base.Throwables;
+import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import com.google.inject.name.Names;
public class BasePayloadTest {
- protected Injector i = Guice.createInjector(Rocoto.expandVariables(new ConfigurationModule() {
- protected void bindConfigurations() {
- bindProperties(new SwiftApiMetadata().getDefaultProperties());
+ protected Injector i = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ Properties expanded = new ExpandProperties().apply(new SwiftApiMetadata().getDefaultProperties());
+ Names.bindProperties(binder(), expanded);
bind(DateAdapter.class).to(Iso8601DateAdapter.class);
}
- }), new GsonModule());
+ }, new GsonModule());
protected GeneratedHttpRequest requestForArgs(List<Object> args) {
try {
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
index 10123f6..55e8424 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -38,10 +38,7 @@
</scm>
<properties>
- <jclouds.osgi.import>
- org.nnsoft.guice.rocoto*;version="[6.1,7)",
- *
- </jclouds.osgi.import>
+ <jclouds.osgi.import>*</jclouds.osgi.import>
<jclouds.osgi.export>org.jclouds*;version=${project.version};-noimport:=true</jclouds.osgi.export>
<jclouds.osgi.activator>org.jclouds.osgi.Activator</jclouds.osgi.activator>
</properties>
@@ -61,11 +58,6 @@
<artifactId>guice</artifactId>
</dependency>
<dependency>
- <groupId>org.99soft.guice</groupId>
- <artifactId>rocoto</artifactId>
- <version>6.2</version>
- </dependency>
- <dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/src/main/java/org/jclouds/ContextBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/ContextBuilder.java b/core/src/main/java/org/jclouds/ContextBuilder.java
index e8052a9..8ca422f 100644
--- a/core/src/main/java/org/jclouds/ContextBuilder.java
+++ b/core/src/main/java/org/jclouds/ContextBuilder.java
@@ -58,10 +58,10 @@ import org.jclouds.concurrent.config.ConfiguresExecutorService;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.config.BindApiContextWithWildcardExtendsExplicitAndRawType;
import org.jclouds.config.BindNameToContext;
-import org.jclouds.config.BindPropertiesToExpandedValues;
import org.jclouds.domain.Credentials;
import org.jclouds.events.config.ConfiguresEventBus;
import org.jclouds.events.config.EventBusModule;
+import org.jclouds.functions.ExpandProperties;
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
import org.jclouds.javax.annotation.Nullable;
@@ -314,7 +314,7 @@ public class ContextBuilder {
Properties resolved = resolveProperties(unexpanded, providerId, keysToResolve, optionalKeys);
- Properties expanded = expandProperties(resolved);
+ Properties expanded = new ExpandProperties().apply(resolved);
Supplier<Credentials> credentialsSupplier = buildCredentialsSupplier(expanded);
@@ -351,10 +351,10 @@ public class ContextBuilder {
private Properties currentStateToUnexpandedProperties() {
Properties defaults = new Properties();
- defaults.putAll(apiMetadata.getDefaultProperties());
+ putAllAsString(apiMetadata.getDefaultProperties(), defaults);
defaults.setProperty(PROPERTY_PROVIDER, providerId);
if (providerMetadata.isPresent()) {
- defaults.putAll(providerMetadata.get().getDefaultProperties());
+ putAllAsString(providerMetadata.get().getDefaultProperties(), defaults);
defaults.setProperty(PROPERTY_ISO3166_CODES, Joiner.on(',').join(providerMetadata.get().getIso3166Codes()));
}
if (endpoint.isPresent())
@@ -367,21 +367,22 @@ public class ContextBuilder {
if (credential != null)
defaults.setProperty(PROPERTY_CREDENTIAL, credential);
if (overrides.isPresent())
- defaults.putAll(checkNotNull(overrides.get(), "overrides"));
- defaults.putAll(propertiesPrefixedWithJcloudsApiOrProviderId(getSystemProperties(), apiMetadata.getId(), providerId));
+ putAllAsString(overrides.get(), defaults);
+ putAllAsString(propertiesPrefixedWithJcloudsApiOrProviderId(getSystemProperties(), apiMetadata.getId(), providerId), defaults);
return defaults;
}
+ private static void putAllAsString(Map<?, ?> source, Properties target) {
+ for (Map.Entry<?, ?> entry : source.entrySet()) {
+ target.setProperty(entry.getKey().toString(), entry.getValue().toString());
+ }
+ }
+
@VisibleForTesting
protected Properties getSystemProperties() {
return System.getProperties();
}
-
- private Properties expandProperties(final Properties resolved) {
- return Guice.createInjector(GUICE_STAGE, new BindPropertiesToExpandedValues(resolved)).getInstance(Properties.class);
- }
-
public static Injector buildInjector(String name, ProviderMetadata providerMetadata, Supplier<Credentials> creds, List<Module> inputModules) {
List<Module> modules = newArrayList();
modules.addAll(inputModules);
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/src/main/java/org/jclouds/config/BindPropertiesToExpandedValues.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/config/BindPropertiesToExpandedValues.java b/core/src/main/java/org/jclouds/config/BindPropertiesToExpandedValues.java
deleted file mode 100644
index e3ae30b..0000000
--- a/core/src/main/java/org/jclouds/config/BindPropertiesToExpandedValues.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.jclouds.config;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Properties;
-
-import javax.inject.Singleton;
-
-import org.jclouds.internal.FilterStringsBoundToInjectorByName;
-import org.nnsoft.guice.rocoto.Rocoto;
-import org.nnsoft.guice.rocoto.configuration.ConfigurationModule;
-
-import com.google.common.base.Predicates;
-import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
-
-/**
- * expands properties.
- */
-public class BindPropertiesToExpandedValues extends AbstractModule {
- private final Properties resolved;
-
- public BindPropertiesToExpandedValues(Properties resolved) {
- this.resolved = checkNotNull(resolved, "resolved");
- }
-
- @Override
- protected void configure() {
- install(Rocoto.expandVariables(new ConfigurationModule() {
-
- @Override
- protected void bindConfigurations() {
- bindProperties(resolved);
- }
-
- @Provides
- @Singleton
- protected Properties expanded(FilterStringsBoundToInjectorByName filterStringsBoundByName) {
- Properties props = new Properties();
- props.putAll(filterStringsBoundByName.apply(Predicates.<String> alwaysTrue()));
- return props;
- }
- }));
- }
-
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/src/main/java/org/jclouds/functions/ExpandProperties.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/functions/ExpandProperties.java b/core/src/main/java/org/jclouds/functions/ExpandProperties.java
new file mode 100644
index 0000000..d75dc8d
--- /dev/null
+++ b/core/src/main/java/org/jclouds/functions/ExpandProperties.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+
+/**
+ * Resolves the values of the properties so they can be inferred from other
+ * properties.
+ */
+public class ExpandProperties implements Function<Properties, Properties> {
+
+ // Matches variables in a string such as ${foo.bar}
+ private static final Pattern VAR = Pattern.compile("\\$\\{[^\\}]+}");
+
+ @Override
+ public Properties apply(final Properties properties) {
+ checkNotNull(properties, "properties cannot be null");
+
+ // Only expand the properties that are Strings
+ Map<String, String> stringProperties = Maps.toMap(properties.stringPropertyNames(),
+ new Function<String, String>() {
+ @Override
+ public String apply(String input) {
+ return properties.getProperty(input);
+ }
+ });
+
+ boolean pendingReplacements = true;
+ Map<String, String> propertiesToResolve = new HashMap<String, String>(stringProperties);
+
+ while (pendingReplacements) {
+ Map<String, String> leafs = leafs(propertiesToResolve);
+ if (leafs.isEmpty()) {
+ break;
+ }
+ pendingReplacements = resolveProperties(propertiesToResolve, leafs);
+ }
+
+ // Replace the values with the resolved ones
+ Properties resolved = new Properties();
+ resolved.putAll(properties);
+ for (Map.Entry<String, String> entry : propertiesToResolve.entrySet()) {
+ resolved.setProperty(entry.getKey(), entry.getValue());
+ }
+
+ return resolved;
+ }
+
+ private Map<String, String> leafs(Map<String, String> input) {
+ return Maps.filterValues(input, new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ Matcher m = VAR.matcher(input);
+ return !m.find();
+ }
+ });
+ }
+
+ private boolean resolveProperties(Map<String, String> properties, Map<String, String> variables) {
+ boolean anyReplacementDone = false;
+ for (String key : properties.keySet()) {
+ StringBuffer sb = new StringBuffer();
+ Matcher m = VAR.matcher(properties.get(key));
+ while (m.find()) {
+ String match = m.group();
+ // Remove the ${} from the matched variable
+ String var = match.substring(2, match.length() - 1);
+ // Avoid recursive properties. Only get he value if the variable
+ // is different than the current key
+ Optional<String> value = var.equals(key) ? Optional.<String> absent() : Optional.fromNullable(variables
+ .get(var));
+ // Replace by the value or leave the original value
+ m.appendReplacement(sb, value.or("\\" + match));
+ if (value.isPresent()) {
+ anyReplacementDone = true;
+ }
+ }
+ m.appendTail(sb);
+ properties.put(key, sb.toString());
+ }
+ return anyReplacementDone;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/src/test/java/org/jclouds/ContextBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/jclouds/ContextBuilderTest.java b/core/src/test/java/org/jclouds/ContextBuilderTest.java
index ac59433..cdceeea 100644
--- a/core/src/test/java/org/jclouds/ContextBuilderTest.java
+++ b/core/src/test/java/org/jclouds/ContextBuilderTest.java
@@ -74,13 +74,13 @@ public class ContextBuilderTest {
assertEquals(endpoint, URI.create("http://foo.service.com"));
}
- @Test
- public void testContextName() {
- ContextBuilder withNoName = testContextBuilder().endpoint("http://${jclouds.identity}.service.com").name("mytest")
- .credentials("foo", "bar");
- Context context = withNoName.build();
- assertEquals(context.getName(), "mytest");
- }
+ @Test
+ public void testContextName() {
+ ContextBuilder withNoName = testContextBuilder().endpoint("http://${jclouds.identity}.service.com").name("mytest")
+ .credentials("foo", "bar");
+ Context context = withNoName.build();
+ assertEquals(context.getName(), "mytest");
+ }
@Test
public void testProviderMetadataBoundWithCorrectEndpoint() {
@@ -129,7 +129,28 @@ public class ContextBuilderTest {
String version = withVersionInProps.buildInjector().getInstance(Key.get(String.class, ApiVersion.class));
assertEquals(version, "1.1");
}
-
+
+ @Test
+ public void testAllPropertiesAreStrings() {
+ Properties overrides = new Properties();
+ overrides.setProperty("foo", "bar");
+ overrides.put("one", 1);
+ overrides.put("two", 2.0f);
+ overrides.put("true", true);
+ overrides.put("object", new Object() {
+ @Override
+ public String toString() {
+ return "object";
+ }
+ });
+ Context withObjectsInProps = testContextBuilder().overrides(overrides).build();
+ Properties resolved = withObjectsInProps.getProviderMetadata().getDefaultProperties();
+ assertEquals(resolved.getProperty("foo"), "bar");
+ assertEquals(resolved.getProperty("one"), "1");
+ assertEquals(resolved.getProperty("true"), "true");
+ assertEquals(resolved.getProperty("object"), "object");
+ }
+
@Test
public void testAddHttpModuleIfNotPresent() {
List<Module> modules = Lists.newArrayList();
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/src/test/java/org/jclouds/apis/JcloudsTestBlobStoreApiMetadata.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/jclouds/apis/JcloudsTestBlobStoreApiMetadata.java b/core/src/test/java/org/jclouds/apis/JcloudsTestBlobStoreApiMetadata.java
index 485ebe0..9ccf6d9 100644
--- a/core/src/test/java/org/jclouds/apis/JcloudsTestBlobStoreApiMetadata.java
+++ b/core/src/test/java/org/jclouds/apis/JcloudsTestBlobStoreApiMetadata.java
@@ -27,7 +27,7 @@ import com.google.auto.service.AutoService;
* Implementation of {@link ApiMetadata} for testing.
*/
@AutoService(ApiMetadata.class)
-public class JcloudsTestBlobStoreApiMetadata extends BaseHttpApiMetadata {
+public class JcloudsTestBlobStoreApiMetadata extends BaseHttpApiMetadata<IntegrationTestClient> {
public static Builder builder() {
return new Builder();
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/src/test/java/org/jclouds/config/BindPropertiesToExpandedValuesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/jclouds/config/BindPropertiesToExpandedValuesTest.java b/core/src/test/java/org/jclouds/config/BindPropertiesToExpandedValuesTest.java
deleted file mode 100644
index 35ce5cc..0000000
--- a/core/src/test/java/org/jclouds/config/BindPropertiesToExpandedValuesTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.jclouds.config;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Properties;
-
-import org.testng.annotations.Test;
-
-import com.google.inject.Guice;
-
-/**
- * Tests behavior of BindPropertiesToExpandedValues
- */
-@Test(groups = "unit", testName = "BindPropertiesToExpandedValuesTest")
-public class BindPropertiesToExpandedValuesTest {
-
- @Test
- public void testExpand() {
- Properties input = new Properties();
- input.setProperty("id", "1234");
- input.setProperty("path", "path:${id}");
- Properties output = Guice.createInjector(new BindPropertiesToExpandedValues(input)).getInstance(Properties.class);
-
- Properties expected = new Properties();
- expected.setProperty("id", "1234");
- expected.setProperty("path", "path:1234");
- assertEquals(output, expected);
- }
-}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/7053a787/core/src/test/java/org/jclouds/functions/ExpandPropertiesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/jclouds/functions/ExpandPropertiesTest.java b/core/src/test/java/org/jclouds/functions/ExpandPropertiesTest.java
new file mode 100644
index 0000000..e2a721f
--- /dev/null
+++ b/core/src/test/java/org/jclouds/functions/ExpandPropertiesTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jclouds.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "ExpandPropertiesTest")
+public class ExpandPropertiesTest {
+
+ @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "properties cannot be null")
+ public void testPropertiesMandatory() {
+ new ExpandProperties().apply(null);
+ }
+
+ @Test
+ public void testResolveProperties() {
+ Properties props = new Properties();
+ props.put("number", 1);
+ props.put("two", "2");
+ props.put("greeting", "hello");
+ props.put("simple", "simple: ${greeting}");
+ props.put("nested", "nested: ${simple}");
+ props.put("mixed", "mixed: ${nested} and ${simple}");
+ props.put("unexisting", "${foobar} substitution");
+ props.put("recursive", "variable5 ${recursive} recursive ${unexisting}");
+ props.put("characters{{$$", "characters");
+ props.put("ugly", "substitute: ${characters{{$$}");
+
+ Properties resolved = new ExpandProperties().apply(props);
+
+ assertEquals(resolved.size(), props.size());
+ assertEquals(resolved.get("number"), 1);
+ assertEquals(resolved.get("two"), "2");
+ assertEquals(resolved.get("greeting"), "hello");
+ assertEquals(resolved.get("simple"), "simple: hello");
+ assertEquals(resolved.get("nested"), "nested: simple: hello");
+ assertEquals(resolved.get("mixed"), "mixed: nested: simple: hello and simple: hello");
+ assertEquals(resolved.get("unexisting"), "${foobar} substitution");
+ assertEquals(resolved.get("recursive"), "variable5 ${recursive} recursive ${unexisting}");
+ assertEquals(resolved.get("ugly"), "substitute: characters");
+ }
+
+ @Test
+ public void testNoLeafs() {
+ Properties props = new Properties();
+ props.put("one", "${two}");
+ props.put("two", "${one}");
+
+ Properties resolved = new ExpandProperties().apply(props);
+
+ assertEquals(resolved.size(), props.size());
+ assertEquals(resolved.get("one"), "${two}");
+ assertEquals(resolved.get("two"), "${one}");
+ }
+
+}