Open Source Repository

Home /jodd/jodd-3.3.2 | Repository Home



jodd/props/PropsData.java
// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.

package jodd.props;

import jodd.bean.BeanTemplateMacroResolver;
import jodd.bean.BeanTemplateParser;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;

/**
 * Props data storage for base and profile properties.
 * Properties can be lookuped and modified only through this
 * class.
 */
public class PropsData implements Cloneable {

  private static final int MAX_INNER_MACROS = 100;

  private static final String APPEND_SEPARATOR = ",";

  protected final HashMap<String, PropsValue> properties;          // base properties

  protected final HashMap<String, Map<String, PropsValue>> profiles;    // profile properties

  protected final BeanTemplateParser beanTemplateParser;

  /**
   * If set, duplicate props will be appended to the end, separated by comma.
   */
  protected boolean appendDuplicateProps;

  public PropsData() {
    this(new HashMap<String, PropsValue>()new HashMap<String, Map<String, PropsValue>>());
  }

  protected PropsData(HashMap<String, PropsValue> properties, HashMap<String, Map<String, PropsValue>> profiles) {
    this.properties = properties;
    this.profiles = profiles;

    this.beanTemplateParser = new BeanTemplateParser();
    beanTemplateParser.setResolveEscapes(false);
    beanTemplateParser.setReplaceMissingKey(false);
  }

  @Override
  public PropsData clone() {
    HashMap<String, PropsValue> newBase = new HashMap<String, PropsValue>();
    HashMap<String, Map<String, PropsValue>> newProfiles = new HashMap<String, Map<String, PropsValue>>();

    newBase.putAll(properties);
    for (Map.Entry<String, Map<String, PropsValue>> entry : profiles.entrySet()) {
      Map<String, PropsValue> map = new HashMap<String, PropsValue>(entry.getValue().size());
      map.putAll(entry.getValue());
      newProfiles.put(entry.getKey(), map);
    }

    PropsData pd = new PropsData(newBase, newProfiles);
    pd.appendDuplicateProps = appendDuplicateProps;

    return pd;
  }

  // ---------------------------------------------------------------- misc

  /**
   * Puts key-value pair into the map, with respect of appending duplicate properties
   */
  protected void put(Map<String, PropsValue> map, String key, String value) {
    if (appendDuplicateProps) {
      PropsValue pv = map.get(key);
      if (pv != null) {
        value = pv.value + APPEND_SEPARATOR + value;
      }
    }
    map.put(key, new PropsValue(value));
  }

  // ---------------------------------------------------------------- properties

  /**
   * Counts base properties.
   */
  public int countBaseProperties() {
    return properties.size();
  }

  /**
   * Adds base property.
   */
  public void putBaseProperty(String key, String value) {
    put(properties, key, value);
  }

  /**
   * Returns base property or <code>null</code> if it doesn't exist.
   */
  public PropsValue getBaseProperty(String key) {
    return properties.get(key);
  }

  // ---------------------------------------------------------------- profiles

  /**
   * Counts profile properties. Note: this method is not
   * that easy on execution.
   */
  public int countProfileProperties() {
    HashSet<String> profileKeys = new HashSet<String>();

    for (Map<String, PropsValue> map : profiles.values()) {
      for (String key : map.keySet()) {
        if (properties.containsKey(key== false) {
          profileKeys.add(key);
        }
      }
    }

    return profileKeys.size();
  }

  /**
   * Adds profile property.
   */
  public void putProfileProperty(String key, String value, String profile) {
    Map<String, PropsValue> map = profiles.get(profile);
    if (map == null) {
      map = new HashMap<String, PropsValue>();
      profiles.put(profile, map);
    }
    put(map, key, value);
  }

  /**
   * Returns profile property.
   */
  public PropsValue getProfileProperty(String profile, String key) {
    Map<String, PropsValue> profileMap = profiles.get(profile);
    if (profileMap == null) {
      return null;
    }
    return profileMap.get(key);
  }


  // ---------------------------------------------------------------- lookup

  /**
   * Lookup props value through profiles and base properties.
   */
  protected String lookupValue(String key, String... profiles) {
    if (profiles != null) {
      nextprofile:
      for (String profile : profiles) {
        while (true) {
          Map<String, PropsValue> profileMap = this.profiles.get(profile);
          if (profileMap == null) {
            continue nextprofile;
          }
          PropsValue value = profileMap.get(key);
          if (value != null) {
            return value.getValue();
          }
          int ndx = profile.lastIndexOf('.');
          if (ndx == -1) {
            break;
          }
          profile = profile.substring(0, ndx);
        }
      }
    }
    PropsValue value = getBaseProperty(key);
    return value == null ?  null : value.getValue();
  }


  // ---------------------------------------------------------------- resolve

  /**
   * Resolves all macros in this props set.
   */
  public void resolveMacros() {
    int loopCount = 0;

    while (loopCount++ < MAX_INNER_MACROS) {
      boolean replaced = resolveMacros(this.properties, null);

      for (Map.Entry<String, Map<String, PropsValue>> entry : profiles.entrySet()) {
        String profile = entry.getKey();
        replaced = resolveMacros(entry.getValue(), profile|| replaced;
      }

      if (replaced == false) {
        break;
      }
    }
  }

  protected boolean resolveMacros(Map<String, PropsValue> map, final String profile) {
    boolean replaced = false;

    BeanTemplateMacroResolver macroResolver = new BeanTemplateMacroResolver() {
      public Object resolve(String name) {
        return lookupValue(name, profile);
      }
    };

    for (Map.Entry<String, PropsValue> entry : map.entrySet()) {
      PropsValue pv = entry.getValue();

      String newValue = beanTemplateParser.parse(pv.value, macroResolver);

      if (newValue.equals(pv.value== false) {
        pv.resolved = newValue;
        replaced = true;
      else {
        pv.resolved = null;
      }
    }
    return replaced;
  }

  // ---------------------------------------------------------------- extract

  /**
   * Extract props as <code>Properties</code>.
   */
  public Properties extractProperties(String... profiles) {
    Properties properties = new Properties();
    if (profiles != null) {
      for (String profile : profiles) {
        while (true) {
          Map<String, PropsValue> map  = this.profiles.get(profile);
          if (map != null) {
            extract(properties, map);
          }

          int ndx = profile.indexOf('.');
          if (ndx == -1) {
            break;
          }
          profile = profile.substring(0, ndx);
        }
      }
    }
    extract(properties, this.properties);
    return properties;
  }

  protected void extract(Properties properties, Map<String, PropsValue> map) {
    for (Map.Entry<String, PropsValue> entry : map.entrySet()) {
      String key = entry.getKey();
      String existingValue = properties.getProperty(key);
      if (existingValue == null) {
        properties.setProperty(key, entry.getValue().getValue());
      }
    }
  }

}