|
@@ -844,25 +844,101 @@ public class Configuration implements Iterable<Map.Entry<String,String>>,
|
|
|
resources.add(resource); // add to resources
|
|
|
reloadConfiguration();
|
|
|
}
|
|
|
-
|
|
|
- private static final Pattern VAR_PATTERN =
|
|
|
- Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}");
|
|
|
|
|
|
private static final int MAX_SUBST = 20;
|
|
|
|
|
|
+ private static final int SUB_START_IDX = 0;
|
|
|
+ private static final int SUB_END_IDX = SUB_START_IDX + 1;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This is a manual implementation of the following regex
|
|
|
+ * "\\$\\{[^\\}\\$\u0020]+\\}". It can be 15x more efficient than
|
|
|
+ * a regex matcher as demonstrated by HADOOP-11506. This is noticeable with
|
|
|
+ * Hadoop apps building on the assumption Configuration#get is an O(1)
|
|
|
+ * hash table lookup, especially when the eval is a long string.
|
|
|
+ *
|
|
|
+ * @param eval a string that may contain variables requiring expansion.
|
|
|
+ * @return a 2-element int array res such that
|
|
|
+ * eval.substring(res[0], res[1]) is "var" for the left-most occurrence of
|
|
|
+ * ${var} in eval. If no variable is found -1, -1 is returned.
|
|
|
+ */
|
|
|
+ private static int[] findSubVariable(String eval) {
|
|
|
+ int[] result = {-1, -1};
|
|
|
+
|
|
|
+ int matchStart;
|
|
|
+ int leftBrace;
|
|
|
+
|
|
|
+ // scanning for a brace first because it's less frequent than $
|
|
|
+ // that can occur in nested class names
|
|
|
+ //
|
|
|
+ match_loop:
|
|
|
+ for (matchStart = 1, leftBrace = eval.indexOf('{', matchStart);
|
|
|
+ // minimum left brace position (follows '$')
|
|
|
+ leftBrace > 0
|
|
|
+ // right brace of a smallest valid expression "${c}"
|
|
|
+ && leftBrace + "{c".length() < eval.length();
|
|
|
+ leftBrace = eval.indexOf('{', matchStart)) {
|
|
|
+ int matchedLen = 0;
|
|
|
+ if (eval.charAt(leftBrace - 1) == '$') {
|
|
|
+ int subStart = leftBrace + 1; // after '{'
|
|
|
+ for (int i = subStart; i < eval.length(); i++) {
|
|
|
+ switch (eval.charAt(i)) {
|
|
|
+ case '}':
|
|
|
+ if (matchedLen > 0) { // match
|
|
|
+ result[SUB_START_IDX] = subStart;
|
|
|
+ result[SUB_END_IDX] = subStart + matchedLen;
|
|
|
+ break match_loop;
|
|
|
+ }
|
|
|
+ // fall through to skip 1 char
|
|
|
+ case ' ':
|
|
|
+ case '$':
|
|
|
+ matchStart = i + 1;
|
|
|
+ continue match_loop;
|
|
|
+ default:
|
|
|
+ matchedLen++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // scanned from "${" to the end of eval, and no reset via ' ', '$':
|
|
|
+ // no match!
|
|
|
+ break match_loop;
|
|
|
+ } else {
|
|
|
+ // not a start of a variable
|
|
|
+ //
|
|
|
+ matchStart = leftBrace + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Attempts to repeatedly expand the value {@code expr} by replacing the
|
|
|
+ * left-most substring of the form "${var}" in the following precedence order
|
|
|
+ * <ol>
|
|
|
+ * <li>by the value of the Java system property "var" if defined</li>
|
|
|
+ * <li>by the value of the configuration key "var" if defined</li>
|
|
|
+ * </ol>
|
|
|
+ *
|
|
|
+ * If var is unbounded the current state of expansion "prefix${var}suffix" is
|
|
|
+ * returned.
|
|
|
+ *
|
|
|
+ * @param expr the literal value of a config key
|
|
|
+ * @return null if expr is null, otherwise the value resulting from expanding
|
|
|
+ * expr using the algorithm above.
|
|
|
+ * @throws IllegalArgumentException when more than
|
|
|
+ * {@link Configuration#MAX_SUBST} replacements are required
|
|
|
+ */
|
|
|
private String substituteVars(String expr) {
|
|
|
if (expr == null) {
|
|
|
return null;
|
|
|
}
|
|
|
- Matcher match = VAR_PATTERN.matcher("");
|
|
|
String eval = expr;
|
|
|
- for(int s=0; s<MAX_SUBST; s++) {
|
|
|
- match.reset(eval);
|
|
|
- if (!match.find()) {
|
|
|
+ for (int s = 0; s < MAX_SUBST; s++) {
|
|
|
+ final int[] varBounds = findSubVariable(eval);
|
|
|
+ if (varBounds[SUB_START_IDX] == -1) {
|
|
|
return eval;
|
|
|
}
|
|
|
- String var = match.group();
|
|
|
- var = var.substring(2, var.length()-1); // remove ${ .. }
|
|
|
+ final String var = eval.substring(varBounds[SUB_START_IDX],
|
|
|
+ varBounds[SUB_END_IDX]);
|
|
|
String val = null;
|
|
|
try {
|
|
|
val = System.getProperty(var);
|
|
@@ -875,8 +951,12 @@ public class Configuration implements Iterable<Map.Entry<String,String>>,
|
|
|
if (val == null) {
|
|
|
return eval; // return literal ${var}: var is unbound
|
|
|
}
|
|
|
+ final int dollar = varBounds[SUB_START_IDX] - "${".length();
|
|
|
+ final int afterRightBrace = varBounds[SUB_END_IDX] + "}".length();
|
|
|
// substitute
|
|
|
- eval = eval.substring(0, match.start())+val+eval.substring(match.end());
|
|
|
+ eval = eval.substring(0, dollar)
|
|
|
+ + val
|
|
|
+ + eval.substring(afterRightBrace);
|
|
|
}
|
|
|
throw new IllegalStateException("Variable substitution depth too large: "
|
|
|
+ MAX_SUBST + " " + expr);
|