public Set<String> extractPlaceholderKeys(String propertyString) {
		Set<String> placeholderKeys = Sets.newHashSet();

		if (!isPlaceholder(propertyString)) {
			return placeholderKeys;
		}

		Stack<String> stack = new Stack<>();
		stack.push(propertyString);

		while (!stack.isEmpty()) {
			String strVal = stack.pop();
			int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
			if (startIndex == -1) {
				placeholderKeys.add(strVal);
				continue;
			}
			int endIndex = findPlaceholderEndIndex(strVal, startIndex);
			if (endIndex == -1) {
				// invalid placeholder?
				continue;
			}

			String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);

			// ${some.key:other.key}
			if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
				stack.push(placeholderCandidate);
			}
			else {
				// some.key:${some.other.key:100}
				int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);

				if (separatorIndex == -1) {
					stack.push(placeholderCandidate);
				}
				else {
					stack.push(placeholderCandidate.substring(0, separatorIndex));
					String defaultValuePart =
							normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
					if (!Strings.isNullOrEmpty(defaultValuePart)) {
						stack.push(defaultValuePart);
					}
				}
			}

			// has remaining part, e.g. ${a}.${b}
			if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
				String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
				if (!Strings.isNullOrEmpty(remainingPart)) {
					stack.push(remainingPart);
				}
			}
		}

		return placeholderKeys;
	}
@Test
	public void extractNormalPlaceholderKeysTest() {
		final String placeholderCase = "${some.key}";
		final String placeholderCase1 = "${some.key:${some.other.key:100}}";
		final String placeholderCase2 = "${${some.key}}";
		final String placeholderCase3 = "${${some.key:other.key}}";
		final String placeholderCase4 = "${${some.key}:${another.key}}";
		final String placeholderCase5 = "#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')}";

		Set<String> placeholderKeys = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase);
		assertThat(placeholderKeys.size()).isEqualTo(1);
		assertThat(placeholderKeys).contains("some.key");

		Set<String> placeholderKeys1 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase1);
		assertThat(placeholderKeys1.size()).isEqualTo(2);
		assertThat(placeholderKeys1).contains("some.key");
		assertThat(placeholderKeys1).contains("some.other.key");

		Set<String> placeholderKeys2 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase2);
		assertThat(placeholderKeys2.size()).isEqualTo(1);
		assertThat(placeholderKeys2).contains("some.key");

		Set<String> placeholderKeys3 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase3);
		assertThat(placeholderKeys3.size()).isEqualTo(1);
		assertThat(placeholderKeys3).contains("some.key");

		Set<String> placeholderKeys4 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase4);
		assertThat(placeholderKeys4.size()).isEqualTo(2);
		assertThat(placeholderKeys4).contains("some.key");
		assertThat(placeholderKeys4).contains("another.key");

		Set<String> placeholderKeys5 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase5);
		assertThat(placeholderKeys5.size()).isEqualTo(2);
		assertThat(placeholderKeys5).contains("some.key");
		assertThat(placeholderKeys5).contains("another.key");
	}