content: package com.ctrip.framework.apollo.internals;
  
  import java.util.Date;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Set;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.atomic.AtomicLong;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.ctrip.framework.apollo.Config;
  import com.ctrip.framework.apollo.ConfigChangeListener;
  import com.ctrip.framework.apollo.build.ApolloInjector;
  import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
  import com.ctrip.framework.apollo.enums.PropertyChangeType;
  import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
  import com.ctrip.framework.apollo.model.ConfigChange;
  import com.ctrip.framework.apollo.model.ConfigChangeEvent;
  import com.ctrip.framework.apollo.tracer.Tracer;
  import com.ctrip.framework.apollo.tracer.spi.Transaction;
  import com.ctrip.framework.apollo.util.ConfigUtil;
  import com.ctrip.framework.apollo.util.function.Functions;
  import com.ctrip.framework.apollo.util.parser.Parsers;
  import com.google.common.base.Function;
  import com.google.common.base.Objects;
  import com.google.common.cache.Cache;
  import com.google.common.cache.CacheBuilder;
  import com.google.common.collect.Lists;
  import com.google.common.collect.Maps;
  import com.google.common.collect.Sets;
  
  /**
   * @author Jason Song(song_s@ctrip.com)
   */
  public abstract class AbstractConfig implements Config {
    private static final Logger logger = LoggerFactory.getLogger(AbstractConfig.class);
  
    private static ExecutorService m_executorService;
  
    private List<ConfigChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
    private ConfigUtil m_configUtil;
    private volatile Cache<String, Integer> m_integerCache;
    private volatile Cache<String, Long> m_longCache;
    private volatile Cache<String, Short> m_shortCache;
    private volatile Cache<String, Float> m_floatCache;
    private volatile Cache<String, Double> m_doubleCache;
    private volatile Cache<String, Byte> m_byteCache;
    private volatile Cache<String, Boolean> m_booleanCache;
    private volatile Cache<String, Date> m_dateCache;
    private volatile Cache<String, Long> m_durationCache;
    private Map<String, Cache<String, String[]>> m_arrayCache;
    private List<Cache> allCaches;
    private AtomicLong m_configVersion; //indicate config version
  
    static {
      m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory
          .create("Config", true));
    }
  
    public AbstractConfig() {
        m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
        m_configVersion = new AtomicLong();
        m_arrayCache = Maps.newConcurrentMap();
        allCaches = Lists.newArrayList();
    }
  
    @Override
    public void addChangeListener(ConfigChangeListener listener) {
      if (!m_listeners.contains(listener)) {
        m_listeners.add(listener);
      }
    }
  
    @Override
    public Integer getIntProperty(String key, Integer defaultValue) {
      try {
        if (m_integerCache == null) {
          synchronized (this) {
            if (m_integerCache == null) {
              m_integerCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_INT_FUNCTION, m_integerCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getIntProperty for %s failed, return default value %d", key,
                defaultValue), ex));
      }
      return defaultValue;
    }
  
    @Override
    public Long getLongProperty(String key, Long defaultValue) {
      try {
        if (m_longCache == null) {
          synchronized (this) {
            if (m_longCache == null) {
              m_longCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_LONG_FUNCTION, m_longCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getLongProperty for %s failed, return default value %d", key,
                defaultValue), ex));
      }
      return defaultValue;
    }
  
    @Override
    public Short getShortProperty(String key, Short defaultValue) {
      try {
        if (m_shortCache == null) {
          synchronized (this) {
            if (m_shortCache == null) {
              m_shortCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_SHORT_FUNCTION, m_shortCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getShortProperty for %s failed, return default value %d", key,
                defaultValue), ex));
      }
      return defaultValue;
    }
  
    @Override
    public Float getFloatProperty(String key, Float defaultValue) {
      try {
        if (m_floatCache == null) {
          synchronized (this) {
            if (m_floatCache == null) {
              m_floatCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_FLOAT_FUNCTION, m_floatCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getFloatProperty for %s failed, return default value %f", key,
                defaultValue), ex));
      }
      return defaultValue;
    }
  
    @Override
    public Double getDoubleProperty(String key, Double defaultValue) {
      try {
        if (m_doubleCache == null) {
          synchronized (this) {
            if (m_doubleCache == null) {
              m_doubleCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_DOUBLE_FUNCTION, m_doubleCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getDoubleProperty for %s failed, return default value %f", key,
                defaultValue), ex));
      }
      return defaultValue;
    }
  
    @Override
    public Byte getByteProperty(String key, Byte defaultValue) {
      try {
        if (m_byteCache == null) {
          synchronized (this) {
            if (m_byteCache == null) {
              m_byteCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_BYTE_FUNCTION, m_byteCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getByteProperty for %s failed, return default value %d", key,
                defaultValue), ex));
      }
      return defaultValue;
    }
  
    @Override
    public Boolean getBooleanProperty(String key, Boolean defaultValue) {
      try {
        if (m_booleanCache == null) {
          synchronized (this) {
            if (m_booleanCache == null) {
              m_booleanCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_BOOLEAN_FUNCTION, m_booleanCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getBooleanProperty for %s failed, return default value %b", key,
                defaultValue), ex));
      }
      return defaultValue;
    }
  
    @Override
    public String[] getArrayProperty(String key, final String delimiter, String[] defaultValue) {
      try {
        if (!m_arrayCache.containsKey(delimiter)) {
          synchronized (this) {
            if (!m_arrayCache.containsKey(delimiter)) {
              m_arrayCache.put(delimiter, this.<String[]>newCache());
            }
          }
        }
  
        Cache<String, String[]> cache = m_arrayCache.get(delimiter);
        String[] result = cache.getIfPresent(key);
  
        if (result != null) {
          return result;
        }
  
        return getValueAndStoreToCache(key, new Function<String, String[]>() {
          @Override
          public String[] apply(String input) {
            return input.split(delimiter);
          }
        }, cache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getArrayProperty for %s failed, return default value", key), ex));
      }
      return defaultValue;
    }
  
    @Override
    public <T extends Enum<T>> T getEnumProperty(String key, Class<T> enumType, T defaultValue) {
      try {
        String value = getProperty(key, null);
  
        if (value != null) {
          return Enum.valueOf(enumType, value);
        }
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getEnumProperty for %s failed, return default value %s", key,
                defaultValue), ex));
      }
  
      return defaultValue;
    }
  
    @Override
    public Date getDateProperty(String key, Date defaultValue) {
      try {
        if (m_dateCache == null) {
          synchronized (this) {
            if (m_dateCache == null) {
              m_dateCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_DATE_FUNCTION, m_dateCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getDateProperty for %s failed, return default value %s", key,
                defaultValue), ex));
      }
  
      return defaultValue;
    }
  
    @Override
    public Date getDateProperty(String key, String format, Date defaultValue) {
      try {
        String value = getProperty(key, null);
  
        if (value != null) {
          return Parsers.forDate().parse(value, format);
        }
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getDateProperty for %s failed, return default value %s", key,
                defaultValue), ex));
      }
  
      return defaultValue;
    }
  
    @Override
    public Date getDateProperty(String key, String format, Locale locale, Date defaultValue) {
      try {
        String value = getProperty(key, null);
  
        if (value != null) {
          return Parsers.forDate().parse(value, format, locale);
        }
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getDateProperty for %s failed, return default value %s", key,
                defaultValue), ex));
      }
  
      return defaultValue;
    }
  
    @Override
    public long getDurationProperty(String key, long defaultValue) {
      try {
        if (m_durationCache == null) {
          synchronized (this) {
            if (m_durationCache == null) {
              m_durationCache = newCache();
            }
          }
        }
  
        return getValueFromCache(key, Functions.TO_DURATION_FUNCTION, m_durationCache, defaultValue);
      } catch (Throwable ex) {
        Tracer.logError(new ApolloConfigException(
            String.format("getDurationProperty for %s failed, return default value %d", key,
                defaultValue), ex));
      }
  
      return defaultValue;
    }
  
    private <T> T getValueFromCache(String key, Function<String, T> parser, Cache<String, T> cache, T defaultValue) {
      T result = cache.getIfPresent(key);
  
      if (result != null) {
        return result;
      }
  
      return getValueAndStoreToCache(key, parser, cache, defaultValue);
    }
  
    private <T> T getValueAndStoreToCache(String key, Function<String, T> parser, Cache<String, T> cache, T defaultValue) {
      long currentConfigVersion = m_configVersion.get();
      String value = getProperty(key, null);
  
      if (value != null) {
        T result = parser.apply(value);
  
        if (result != null) {
          synchronized (this) {
            if (m_configVersion.get() == currentConfigVersion) {
              cache.put(key, result);
            }
          }
          return result;
        }
      }
  
      return defaultValue;
    }
  
    private <T> Cache<String, T> newCache() {
      Cache<String, T> cache = CacheBuilder.newBuilder()
          .maximumSize(m_configUtil.getMaxConfigCacheSize())
          .expireAfterAccess(m_configUtil.getConfigCacheExpireTime(), m_configUtil.getConfigCacheExpireTimeUnit())
          .build();
      allCaches.add(cache);
      return cache;
    }
  
    /**
     * Clear config cache
     */
    protected void clearConfigCache() {
      synchronized (this) {
        for (Cache c : allCaches) {
          if (c != null) {
            c.invalidateAll();
          }
        }
        m_configVersion.incrementAndGet();
      }
    }
  
    protected void fireConfigChange(final ConfigChangeEvent changeEvent) {
      for (final ConfigChangeListener listener : m_listeners) {
        m_executorService.submit(new Runnable() {
          @Override
          public void run() {
            String listenerName = listener.getClass().getName();
            Transaction transaction = Tracer.newTransaction("Apollo.ConfigChangeListener", listenerName);
            try {
              listener.onChange(changeEvent);
              transaction.setStatus(Transaction.SUCCESS);
            } catch (Throwable ex) {
              transaction.setStatus(ex);
              Tracer.logError(ex);
              logger.error("Failed to invoke config change listener {}", listenerName, ex);
            } finally {
              transaction.complete();
            }
          }
        });
      }
    }
  
    List<ConfigChange> calcPropertyChanges(String namespace, Properties previous,
                                           Properties current) {
      if (previous == null) {
        previous = new Properties();
      }
  
      if (current == null) {
        current = new Properties();
      }
  
      Set<String> previousKeys = previous.stringPropertyNames();
      Set<String> currentKeys = current.stringPropertyNames();
  
      Set<String> commonKeys = Sets.intersection(previousKeys, currentKeys);
      Set<String> newKeys = Sets.difference(currentKeys, commonKeys);
      Set<String> removedKeys = Sets.difference(previousKeys, commonKeys);
  
      List<ConfigChange> changes = Lists.newArrayList();
  
      for (String newKey : newKeys) {
        changes.add(new ConfigChange(namespace, newKey, null, current.getProperty(newKey),
            PropertyChangeType.ADDED));
      }
  
      for (String removedKey : removedKeys) {
        changes.add(new ConfigChange(namespace, removedKey, previous.getProperty(removedKey), null,
            PropertyChangeType.DELETED));
      }
  
      for (String commonKey : commonKeys) {
        String previousValue = previous.getProperty(commonKey);
        String currentValue = current.getProperty(commonKey);
        if (Objects.equal(previousValue, currentValue)) {
          continue;
        }
        changes.add(new ConfigChange(namespace, commonKey, previousValue, currentValue,
            PropertyChangeType.MODIFIED));
      }
  
      return changes;
    }
  }
  