-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #72 from dmlloyd/map
[#70] Implement a helper Map implementation for ConfigSources to use
- Loading branch information
Showing
2 changed files
with
393 additions
and
0 deletions.
There are no files selected for viewing
161 changes: 161 additions & 0 deletions
161
implementation/src/main/java/io/smallrye/config/ConfigSourceMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* | ||
* Copyright 2018 Red Hat, Inc. | ||
* | ||
* 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 io.smallrye.config; | ||
|
||
import java.util.AbstractCollection; | ||
import java.util.AbstractMap; | ||
import java.util.AbstractSet; | ||
import java.util.Collection; | ||
import java.util.Iterator; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.function.BiConsumer; | ||
|
||
import org.eclipse.microprofile.config.spi.ConfigSource; | ||
|
||
/** | ||
* A {@link Map Map<String, String>} which is backed by a {@link ConfigSource}. | ||
* This should not be used to implement {@link ConfigSource#getProperties()} on {@code ConfigSource} | ||
* instances which do not override {@code getPropertyNames()}, as this will result in infinite recursion. | ||
* | ||
* @implNote The key set of the map is the result of calling {@link ConfigSource#getPropertyNames()}; the rest | ||
* of the map operations are derived from this method and {@link ConfigSource#getValue(String)}. | ||
* The values collection and entry set are instantiated lazily and cached. | ||
* The implementation attempts to make no assumptions about the efficiency of the backing implementation and | ||
* prefers the most direct access possible. | ||
* <p> | ||
* The backing collections are assumed to be immutable. | ||
*/ | ||
public class ConfigSourceMap extends AbstractMap<String, String> implements Map<String, String> { | ||
private final ConfigSource delegate; | ||
private Values values; | ||
private EntrySet entrySet; | ||
|
||
/** | ||
* Construct a new instance. | ||
* | ||
* @param delegate the delegate configuration source (must not be {@code null}) | ||
*/ | ||
public ConfigSourceMap(final ConfigSource delegate) { | ||
this.delegate = Objects.requireNonNull(delegate, "delegate must not be null"); | ||
} | ||
|
||
public int size() { | ||
return delegate.getPropertyNames().size(); | ||
} | ||
|
||
public boolean isEmpty() { | ||
// may be cheaper in some cases | ||
return delegate.getPropertyNames().isEmpty(); | ||
} | ||
|
||
public boolean containsKey(final Object key) { | ||
//noinspection SuspiciousMethodCalls - it's OK in this case | ||
return delegate.getPropertyNames().contains(key); | ||
} | ||
|
||
public String get(final Object key) { | ||
return key instanceof String ? delegate.getValue((String) key) : null; | ||
} | ||
|
||
public Set<String> keySet() { | ||
return delegate.getPropertyNames(); | ||
} | ||
|
||
public Collection<String> values() { | ||
Values values = this.values; | ||
if (values == null) return this.values = new Values(); | ||
return values; | ||
} | ||
|
||
public Set<Entry<String, String>> entrySet() { | ||
EntrySet entrySet = this.entrySet; | ||
if (entrySet == null) return this.entrySet = new EntrySet(); | ||
return entrySet; | ||
} | ||
|
||
public void forEach(final BiConsumer<? super String, ? super String> action) { | ||
// superclass is implemented in terms of entry set - expensive! | ||
for (String name : keySet()) { | ||
action.accept(name, delegate.getValue(name)); | ||
} | ||
} | ||
|
||
final class Values extends AbstractCollection<String> implements Collection<String> { | ||
public Iterator<String> iterator() { | ||
return new Itr(delegate.getPropertyNames().iterator()); | ||
} | ||
|
||
public int size() { | ||
return delegate.getPropertyNames().size(); | ||
} | ||
|
||
public boolean isEmpty() { | ||
// may be cheaper in some cases | ||
return delegate.getPropertyNames().isEmpty(); | ||
} | ||
|
||
final class Itr implements Iterator<String> { | ||
private final Iterator<String> iterator; | ||
|
||
Itr(final Iterator<String> iterator) { | ||
this.iterator = iterator; | ||
} | ||
|
||
public boolean hasNext() { | ||
return iterator.hasNext(); | ||
} | ||
|
||
public String next() { | ||
return delegate.getValue(iterator.next()); | ||
} | ||
} | ||
} | ||
|
||
final class EntrySet extends AbstractSet<Entry<String, String>> { | ||
public Iterator<Entry<String, String>> iterator() { | ||
return new Itr(delegate.getPropertyNames().iterator()); | ||
} | ||
|
||
public int size() { | ||
return delegate.getPropertyNames().size(); | ||
} | ||
|
||
public boolean isEmpty() { | ||
// may be cheaper in some cases | ||
return delegate.getPropertyNames().isEmpty(); | ||
} | ||
|
||
final class Itr implements Iterator<Entry<String, String>> { | ||
private final Iterator<String> iterator; | ||
|
||
Itr(final Iterator<String> iterator) { | ||
this.iterator = iterator; | ||
} | ||
|
||
public boolean hasNext() { | ||
return iterator.hasNext(); | ||
} | ||
|
||
public Entry<String, String> next() { | ||
String name = iterator.next(); | ||
return new SimpleImmutableEntry<>(name, delegate.getValue(name)); | ||
} | ||
} | ||
} | ||
} |
232 changes: 232 additions & 0 deletions
232
implementation/src/test/java/io/smallrye/config/ConfigSourceMapTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
/* | ||
* JBoss, Home of Professional Open Source. | ||
* Copyright 2018 Red Hat, Inc., and individual contributors | ||
* as indicated by the @author tags. | ||
* | ||
* 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 io.smallrye.config; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertNull; | ||
import static org.junit.Assert.assertTrue; | ||
import static org.junit.Assert.fail; | ||
|
||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import org.eclipse.microprofile.config.spi.ConfigSource; | ||
import org.junit.Test; | ||
|
||
/** | ||
*/ | ||
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") | ||
public class ConfigSourceMapTest { | ||
@SuppressWarnings("serial") | ||
private static final Map<String, String> MANY_MAP = new HashMap<String, String>() {{ | ||
put("key-one", "12345"); | ||
put("null-valued-key", null); | ||
put("test", "bar"); | ||
put("fruit", "banana"); | ||
}}; | ||
private static final ConfigSource MANY_CONF_SRC = new PropertiesConfigSource(MANY_MAP, "test", 100); | ||
private static final Map<String, String> ONE_MAP = Collections.singletonMap("test", "foo"); | ||
private static final ConfigSource ONE_CONF_SRC = new PropertiesConfigSource(ONE_MAP, "test", 100); | ||
|
||
@Test | ||
public void testClear() { | ||
try { | ||
new ConfigSourceMap(ONE_CONF_SRC).clear(); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testCompute() { | ||
try { | ||
new ConfigSourceMap(ONE_CONF_SRC).compute("piano", (k, v) -> "player"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testComputeIfAbsent() { | ||
try { | ||
//noinspection ExcessiveLambdaUsage | ||
new ConfigSourceMap(ONE_CONF_SRC).computeIfAbsent("piano", k -> "player"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testComputeIfPresent() { | ||
try { | ||
new ConfigSourceMap(ONE_CONF_SRC).computeIfPresent("test", (k, v) -> "bar"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testContainsKey() { | ||
final ConfigSourceMap csm = new ConfigSourceMap(MANY_CONF_SRC); | ||
assertTrue(csm.containsKey("test")); | ||
assertTrue(csm.containsKey("null-valued-key")); | ||
assertFalse(csm.containsKey("missing-key")); | ||
} | ||
|
||
@Test | ||
public void testContainsValue() { | ||
final ConfigSourceMap csm = new ConfigSourceMap(MANY_CONF_SRC); | ||
assertTrue(csm.containsValue("12345")); | ||
assertFalse(csm.containsValue("apple")); | ||
assertTrue(csm.containsValue(null)); | ||
} | ||
|
||
@Test | ||
public void testEquals() { | ||
assertEquals(MANY_MAP, new ConfigSourceMap(MANY_CONF_SRC)); | ||
} | ||
|
||
@Test | ||
public void testForEach() { | ||
final Set<String> need = new HashSet<>(MANY_MAP.keySet()); | ||
final ConfigSourceMap csm = new ConfigSourceMap(MANY_CONF_SRC); | ||
csm.forEach((k, v) -> { | ||
assertEquals(MANY_MAP.get(k), v); | ||
assertTrue(need.remove(k)); | ||
}); | ||
assertTrue("keys left in set", need.isEmpty()); | ||
} | ||
|
||
@Test | ||
public void testGet() { | ||
final ConfigSourceMap csm = new ConfigSourceMap(MANY_CONF_SRC); | ||
assertEquals("bar", csm.get("test")); | ||
assertNull(csm.get("null-valued-key")); | ||
assertNull(csm.get("nope")); | ||
} | ||
|
||
@Test | ||
public void testGetOrDefault() { | ||
final ConfigSourceMap csm = new ConfigSourceMap(MANY_CONF_SRC); | ||
assertEquals("bar", csm.getOrDefault("test", "foo")); | ||
assertNull(csm.getOrDefault("null-valued-key", "oops")); | ||
assertEquals("yes", csm.getOrDefault("nope", "yes")); | ||
} | ||
|
||
@Test | ||
public void testHashCode() { | ||
assertEquals(MANY_MAP.hashCode(), new ConfigSourceMap(MANY_CONF_SRC).hashCode()); | ||
} | ||
|
||
@Test | ||
public void testIsEmpty() { | ||
assertTrue(new ConfigSourceMap(new PropertiesConfigSource(Collections.emptyMap(), "test", 100)).isEmpty()); | ||
assertFalse(new ConfigSourceMap(ONE_CONF_SRC).isEmpty()); | ||
assertFalse(new ConfigSourceMap(MANY_CONF_SRC).isEmpty()); | ||
} | ||
|
||
@Test | ||
public void testKeySet() { | ||
assertEquals(ONE_CONF_SRC.getPropertyNames(), new ConfigSourceMap(ONE_CONF_SRC).keySet()); | ||
assertEquals(MANY_CONF_SRC.getPropertyNames(), new ConfigSourceMap(MANY_CONF_SRC).keySet()); | ||
} | ||
|
||
@Test | ||
public void testMerge() { | ||
try { | ||
new ConfigSourceMap(MANY_CONF_SRC).merge("test", "bar", (k, v) -> "oops"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testPut() { | ||
try { | ||
new ConfigSourceMap(ONE_CONF_SRC).put("bees", "bzzzz"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testPutAll() { | ||
try { | ||
new ConfigSourceMap(ONE_CONF_SRC).putAll(Collections.singletonMap("bees", "bzzzz")); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testPutIfAbsent() { | ||
try { | ||
new ConfigSourceMap(ONE_CONF_SRC).putIfAbsent("bees", "bzzzz"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
new ConfigSourceMap(ONE_CONF_SRC).putIfAbsent("test", "not absent"); | ||
} | ||
|
||
@Test | ||
public void testRemove1() { | ||
try { | ||
new ConfigSourceMap(MANY_CONF_SRC).remove("test"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testRemove2() { | ||
try { | ||
new ConfigSourceMap(MANY_CONF_SRC).remove("test"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testReplace2() { | ||
try { | ||
new ConfigSourceMap(MANY_CONF_SRC).replace("test", "oops"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testReplace3() { | ||
try { | ||
new ConfigSourceMap(MANY_CONF_SRC).replace("test", "bar", "oops"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
try { | ||
// false or exception are OK | ||
assertFalse(new ConfigSourceMap(MANY_CONF_SRC).replace("test", "nope", "oops")); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testReplaceAll() { | ||
try { | ||
new ConfigSourceMap(MANY_CONF_SRC).replaceAll((k, v) -> "oops"); | ||
fail("Expected exception"); | ||
} catch (UnsupportedOperationException expected) {} | ||
} | ||
|
||
@Test | ||
public void testSize() { | ||
assertEquals(MANY_MAP.size(), new ConfigSourceMap(MANY_CONF_SRC).size()); | ||
assertEquals(ONE_MAP.size(), new ConfigSourceMap(ONE_CONF_SRC).size()); | ||
} | ||
} |