docs: private String substituteVars(String expr) {
  		if (expr == null) {
  			return null;
  		}
  		String eval = expr;
  		for(int s = 0; s < MAX_SUBST; s++) {
  			final int[] varBounds = findSubVariable(eval);
  			if (varBounds[SUB_START_IDX] == -1) {
  				return eval;
  			}
  			final String var = eval.substring(varBounds[SUB_START_IDX],
  					varBounds[SUB_END_IDX]);
  			String val = null;
  			if (!restrictSystemProps) {
  				try {
  					if (var.startsWith("env.") && 4 < var.length()) {
  						String v = var.substring(4);
  						int i = 0;
  						for (; i < v.length(); i++) {
  							char c = v.charAt(i);
  							if (c == ':' && i < v.length() - 1 && v.charAt(i + 1) == '-') {
  								val = getenv(v.substring(0, i));
  								if (val == null || val.length() == 0) {
  									val = v.substring(i + 2);
  								}
  								break;
  							} else if (c == '-') {
  								val = getenv(v.substring(0, i));
  								if (val == null) {
  									val = v.substring(i + 1);
  								}
  								break;
  							}
  						}
  						if (i == v.length()) {
  							val = getenv(v);
  						}
  					} else {
  						val = getProperty(var);
  					}
  				} catch (SecurityException se) {
  					LOG.warn("Unexpected SecurityException in Configuration", se);
  				}
  			}
  			if (val == null) {
  				val = getRaw(var);
  			}
  			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();
  			final String refVar = eval.substring(dollar, afterRightBrace);
  
  			// detect self-referential values
  			if (val.contains(refVar)) {
  				return expr; // return original expression if there is a loop
  			}
  
  			// substitute
  			eval = eval.substring(0, dollar)
  					+ val
  					+ eval.substring(afterRightBrace);
  		}
  		throw new IllegalStateException("Variable substitution depth too large: "
  				+ MAX_SUBST + " " + expr);
  	}