code: @SuppressWarnings("el-syntax")
  	public static final String replaceVariables(String text, boolean caseSensitive, ValueProvider... providers) {
  		String replaceText = text;
  		if(providers != null) {		
  			Regex regex = new Regex(VARIABLE_PATTERN, caseSensitive);
  			List<String[]> variables = null;
  			
  			Set<String> unboundVariables = new HashSet<String>();
  			
  			boolean oneVariableBound = true;
  			while(oneVariableBound && !(variables = regex.getAllMatches(replaceText)).isEmpty()) {
  				oneVariableBound = false;			
  				logger.trace("analysing text: '{}'", replaceText);
  				for(String[] groups : variables) {
  					String variable = groups[0];
  					logger.trace("... handling variable '{}'...", variable);
  					String value = null;
  					for(ValueProvider provider : providers) {
  						value = provider.onVariable(variable);
  						if(value != null) {
  							logger.trace("... replacing variable '{}' with value '{}'", variable, value);
  							replaceText = replaceText.replace("${" + variable +"}", value);
  							logger.trace("... text is now '{}'", replaceText);
  							oneVariableBound = true;
  							break;
  						}
  					}
  					if(value == null) {
  						unboundVariables.add(variable);
  					}
  				}
  			}
  		}
  		return replaceText;
  	}
docs: Matches all variables (according to the ${[a-zA-Z_][a-zA-Z0-9_\-]*} pattern)
  and replaces them with the value provided by the set of value provides.
  The processing is repeated until no more valid variable names can be found
  in the input text, or no more variables can be bound (e.g. when some values
  are not available).
  
  @param text
  the text possibly containing variable identifiers.
  @param caseSensitive
  whether the variable names should be treated in a case sensitive way
  (default: true).
  @param providers
  a set of zero or more value providers.
  @return
  the text with all variables bound.