gt:         for (String key : this.stringPropertyNames() ) {
context: /*
   * dataSet - Test Support For Datastores.
   *
   * Copyright (C) 2014-2014 Marko Umek (http://fail-early.com/contact)
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>
   */
  package org.failearly.dataset.util;
  
  import org.apache.commons.lang.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.*;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * ExtendedProperties extends properties with the possibility of using references like {@code ${any.var}} or {@code ${user.home}}.
   */
  public class ExtendedProperties extends Properties {
  
      /**
       * Used for null values.
       */
      public static final String NULL_VALUE = "(null)";
  
      /**
       * Used for empty values.
       */
      public static final String EMPTY_VALUE = "(empty)";
  
      private static final Logger LOGGER = LoggerFactory.getLogger(ExtendedProperties.class);
  
      private final Pattern pattern=Pattern.compile(".*\\$\\{([\\w.]+)\\}.*");
  
      public ExtendedProperties() {
      }
  
      public ExtendedProperties(Properties defaults) {
          super(defaults);
      }
  
  
      /**
       * Resolve all references within values. Replace {@code ${my.property.key}} with the assigned value.
       */
      public final void resolveReferences() {
          final Map<String,String> propertyValues=new HashMap<>();
          for (Map.Entry<Object, Object> entry : this.entrySet()) {
              doResolvePropertyReference("" + entry.getKey(), "" + entry.getValue(), propertyValues);
          }
          updateProperties(propertyValues);
      }
  
      /**
       * Resolve reference(s) within value of {@code key}.
       *
       * @param key property key.
       */
      public final void resolveReferences(String key) {
          final Map<String,String> propertyValues=new HashMap<>();
          doResolvePropertyReference(key, this.getProperty(key), propertyValues);
          updateProperties(propertyValues);
      }
  
      /**
       * Set property value. If the value is {@code null} iit will be replaced with {@value #NULL_VALUE}. {@link #getProperty(String)} replaces this value with
       * {@code null}.
       * @param key property key ({@code not null}.
       * @param value property value ({@code null} is supported).
       * @return see {@link Properties#setProperty(String, String)}
       */
      @Override
      public Object setProperty(String key, String value) {
          Objects.requireNonNull(key,"Property key must not be null");
          if( value==null ) {
              return super.setProperty(key, NULL_VALUE);
          }
          value=StringUtils.trimToEmpty(value);
          if( "".equals(value) ) {
              return super.setProperty(key, EMPTY_VALUE);
          }
          return super.setProperty(key, value);
      }
  
      /**
       * Return the value of the key or throw {@link org.failearly.dataset.util.MissingPropertyException}, if the property is unknown or the value is
       * {@code null}, empty or blank.
       *
       * @param key the property key.
       * @return the value.
       *
       * @throws org.failearly.dataset.util.MissingPropertyException in case of unknown property key or empty or {@code null} value.
       */
      public String getMandatoryProperty(String key) throws MissingPropertyException {
          final String value = getProperty(key);
          if( null==value ) {
              throw new MissingPropertyException(key);
          }
          return value;
      }
  
  
      /**
       * If the property exists the value will be trimmed by {@link org.apache.commons.lang.StringUtils#trimToNull(String)}.
       * So an existing property is only if the value is not empty and not blank.
       * @param key the property key.
       * @return the trimmed value or {@code null}.
       */
      @Override
      public String getProperty(String key) {
          final String value = getPropertyValue(key);
          LOGGER.debug("Access property key '{}' = '{}'", key, value);
          return value;
      }
  
      private String getPropertyValue(String key) {
          final String value = StringUtils.trimToNull(getProperty0(key));
          if( NULL_VALUE.equals(value) ) {
              return null;
          }
          if( EMPTY_VALUE.equals(value) ) {
              return "";
          }
          return value;
      }
  
      private String getProperty0(String key) {
          return super.getProperty(key);
      }
  
      @Override
      public String getProperty(String key, String defaultValue) {
          final String value = getProperty(key);
          return value==null ? defaultValue : value;
      }
  
      private void updateProperties(Map<String, String> propertyValues) {
          for (Map.Entry<String, String> entry : propertyValues.entrySet()) {
              setProperty(entry.getKey(), entry.getValue());
          }
      }
  
      private void doResolvePropertyReference(String key, String value, Map<String, String> propertyValues) {
          if( value != null ) {
              final Matcher matcher = matcher(value);
              if( matcher!=null ) {
                  while (matcher.matches()) {
                      final String referencedKey = referencedPropertyKey(matcher);
                      final String referencedValue = resolveValueOfReferencedKey(referencedKey);
                      checkForEndlessLoop(key, referencedKey, referencedValue);
  
                      value = value.replace(propertyKey(referencedKey), referencedValue);
  
                      matcher.reset(value);
                  }
              }
  
              propertyValues.put(key, value);
          }
          else {
              propertyValues.put(key, null);
          }
      }
  
      private void checkForEndlessLoop(String key, String nextKey, String nextValue) {
          final Matcher matcher=matcher(nextValue);
          if( matcher != null ) {
              final Set<String> keys=new HashSet<>();
              keys.add(key);
              keys.add(nextKey);
  
              while( matcher.matches() ) {
                  final String referencedKey = referencedPropertyKey(matcher);
                  if( keys.contains(referencedKey) ) {
                      throwEndlessRecursionDetected(key, referencedKey);
                  }
  
                  keys.add(referencedKey);
                  final String referencedValue = resolveValueOfReferencedKey(referencedKey);
                  matcher.reset(referencedValue);
              }
          }
      }
  
      private void throwEndlessRecursionDetected(String key, String referencedKey) {
          final String message = "Recursion on property '" + key + "' detected. Variable '" + referencedKey + "' causes endless recursion";
          LOGGER.error(message);
          throw new RuntimeException(message);
      }
  
      private String resolveValueOfReferencedKey(String referencedKey) {
          return this.getProperty(referencedKey, referencedKey);
      }
  
      private static String propertyKey(String propertyKey) {
          return "${" + propertyKey + "}";
      }
  
      private static String referencedPropertyKey(Matcher matcher) {
          return matcher.group(1);
      }
  
  
      private boolean hasReference(String inputValue) {
          return matcher(inputValue)!=null;
      }
  
      private Matcher matcher(String inputValue) {
          final Matcher matcher=pattern.matcher(inputValue);
  
          return matcher.matches() ? matcher : null;
      }
  
      public final Properties toStandardProperties() {
          final Properties copy=new Properties();
  