Cake { @SuppressWarnings("unchecked") public <T> T get(String... key) { List<String> keys = ((key == null) ? new ArrayList<String>() : Arrays.asList(key)) .stream() .flatMap( k -> Arrays.stream( k == null ? new String[] {null} : StringUtils.split(k, Cake.SEPARATOR))) .collect(Collectors.toList()); if (keys.isEmpty()) { throw new IllegalArgumentException("cannot get value for empty key"); } keys.forEach(Cake::validateKey); String fullKey = StringUtils.join(keys, Cake.SEPARATOR); String searchKey = getPrefixWithSeparator(prefixStack) + fullKey; if (entries.containsKey(searchKey)) { return (T) entries.get(searchKey); } LinkedList<String> namespaces = new LinkedList<>(prefixStack); while (!namespaces.isEmpty()) { namespaces.removeLast(); searchKey = getPrefixWithSeparator(namespaces) + fullKey; if (entries.containsKey(searchKey)) { return (T) entries.get(searchKey); } } List<String> candidates = entries.keySet().stream() .filter( k -> { String[] subkeys = StringUtils.split(k, SEPARATOR); if (keys.size() > subkeys.length) { return false; } for (int i = 0; i < keys.size(); i++) { if (!keys.get(keys.size() - 1 - i).equals(subkeys[subkeys.length - 1 - i])) { return false; } } return true; }) .collect(Collectors.toList()); if (candidates.size() == 1) { return (T) entries.get(candidates.get(0)); } else if (candidates.isEmpty()) { throw new RuntimeException( "cake does not contain key '" + fullKey + "; current keys: " + this.entries.keySet()); } else { throw new RuntimeException( "cannot retrieve ambiguous key '" + fullKey + "'; candidates: " + candidates); } } Cake();  Cake(Cake other); static String key(String... subKeys); @SuppressWarnings("unchecked") T get(String... key); @SuppressWarnings("unchecked") T get(Class<T> clazz, String... key); void publish(String key, Object value); void inNamespace(String key, Runnable runnable); String getPublishedKeyForValue(Object value, boolean fullyQualified); @JsonIgnore String getNamespace(); boolean hasContext(); @JsonIgnore T getContext(); @JsonIgnore @SuppressWarnings("unchecked") T getContext(Class<T> clazz); @JsonIgnore T getOrGetContext(String... key); @JsonIgnore T getOrGetContext(Class<T> clazz, String... key); static final String SEPARATOR; }
@Test(expected = RuntimeException.class) public void testGet_throwsOnMissingKey() { cake.get("foo"); }
@Test(expected = IllegalArgumentException.class) public void testGet_throwsOnNullKey() { cake.get(null); }
@Test(expected = IllegalArgumentException.class) public void testGet_throwsOnEmptyString() { cake.get(""); }
@Test(expected = IllegalArgumentException.class) public void testGet_throwsOnNullSubkey() { cake.get("foo", null, "bar"); }