/**
  * Replace ${} style constructions in the given value with the
  * string value of the corresponding data types. This method is
  * static.
  *
  * @param  object  $project  the project that should be used for property look-ups
  * @param  string  $value    the string to be scanned for property references
  * @param  array   $keys     property keys
  * @param  integer $logLevel the level of generated log messages
  * @return string  the replaced string or <code>null</code> if the string
  *                 itself was null
  */
 public static function replaceProperties(Project $project, $value, $keys, $logLevel = Project::MSG_VERBOSE)
 {
     if ($value === null) {
         return null;
     }
     // These are a "hack" to support static callback for preg_replace_callback()
     // make sure these get initialized every time
     self::$propReplaceProperties = $keys;
     self::$propReplaceProject = $project;
     self::$propReplaceLogLevel = $logLevel;
     // Because we're not doing anything special (like multiple passes),
     // regex is the simplest / fastest.  PropertyTask, though, uses
     // the old parsePropertyString() method, since it has more stringent
     // requirements.
     $sb = $value;
     $iteration = 0;
     // loop to recursively replace tokens
     while (strpos($sb, '${') !== false) {
         $sb = preg_replace_callback('/\\$\\{([^\\$}]+)\\}/', array('ProjectConfigurator', 'replacePropertyCallback'), $sb);
         // keep track of iterations so we can break out of otherwise infinite loops.
         $iteration++;
         if ($iteration == 5) {
             return $sb;
         }
     }
     return $sb;
 }