| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.brooklyn.util.core.config; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| import java.util.ConcurrentModificationException; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.function.BiConsumer; |
| import java.util.function.Function; |
| |
| import javax.annotation.Nonnull; |
| |
| import com.fasterxml.jackson.annotation.JsonAutoDetect; |
| import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; |
| import org.apache.brooklyn.config.ConfigKey; |
| import org.apache.brooklyn.config.ConfigKey.HasConfigKey; |
| import org.apache.brooklyn.core.config.ConfigKeys; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.core.flags.TypeCoercions; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.guava.Maybe; |
| import org.apache.brooklyn.util.javalang.JavaClassNames; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.base.Objects; |
| import com.google.common.collect.Sets; |
| |
| /** |
| * Stores config in such a way that usage can be tracked. |
| * Either {@link ConfigKey} or {@link String} keys can be inserted; |
| * they will be stored internally as strings. |
| * It is recommended to use {@link ConfigKey} instances to access, |
| * although in some cases (such as setting fields from flags, or copying a map) |
| * it may be necessary to mark things as used, or put, when only a string key is available. |
| * <p> |
| * This bag is order-preserving and thread-safe except where otherwise indicated, |
| * currently by synching on this instance (but that behaviour may change). |
| * <p> |
| * @author alex |
| */ |
| @JsonAutoDetect(fieldVisibility = Visibility.ANY, isGetterVisibility = Visibility.NONE, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) |
| public class ConfigBag { |
| |
| private static final Logger log = LoggerFactory.getLogger(ConfigBag.class); |
| |
| /** an immutable, empty ConfigBag */ |
| public static final ConfigBag EMPTY = new ConfigBag().setDescription("immutable empty config bag").seal(); |
| |
| protected String description; |
| |
| private Map<String,Object> config; |
| private final Map<String,Object> unusedConfig; |
| private final boolean live; |
| private boolean sealed = false; |
| |
| /** creates a new ConfigBag instance, empty and ready for population */ |
| public static ConfigBag newInstance() { |
| return new ConfigBag(); |
| } |
| |
| /** |
| * Creates an instance that is backed by a "live map" (e.g. storage in a datagrid). |
| * The order-preserving nature of this class is only guaranteed if the |
| * provided storage has those properties. External modifications to the store can cause |
| * {@link ConcurrentModificationException} to be thrown, here or elsewhere. |
| */ |
| public static ConfigBag newLiveInstance(Map<String,Object> storage) { |
| return new ConfigBag(checkNotNull(storage, "storage map must be specified")); |
| } |
| |
| public static ConfigBag newInstance(Map<?, ?> config) { |
| ConfigBag result = new ConfigBag(); |
| result.putAll(config); |
| return result; |
| } |
| |
| /** creates a new ConfigBag instance which includes all of the supplied ConfigBag's values, |
| * but which tracks usage separately (already used values are marked as such, |
| * but uses in the original set will not be marked here, and vice versa) */ |
| public static ConfigBag newInstanceCopying(final ConfigBag configBag) { |
| return new ConfigBag().copy(configBag).setDescription(configBag.getDescription()); |
| } |
| |
| /** creates a new ConfigBag instance which includes all of the supplied ConfigBag's values, |
| * plus an additional set of <ConfigKey,Object> or <String,Object> pairs |
| * <p> |
| * values from the original set which are used here will be marked as used in the original set |
| * (note: this applies even for values which are overridden and the overridden value is used); |
| * however subsequent uses in the original set will not be marked here |
| */ |
| @Beta |
| public static ConfigBag newInstanceExtending(final ConfigBag parentBag) { |
| return new ConfigBagExtendingParent(parentBag); |
| } |
| |
| /** @see #newInstanceExtending(ConfigBag) */ |
| private static class ConfigBagExtendingParent extends ConfigBag { |
| ConfigBag parentBag; |
| private ConfigBagExtendingParent(ConfigBag parentBag) { |
| this.parentBag = parentBag; |
| copy(parentBag); |
| } |
| @Override |
| public void markUsed(String key) { |
| super.markUsed(key); |
| if (parentBag!=null) |
| parentBag.markUsed(key); |
| } |
| } |
| |
| /** As {@link #newInstanceExtending(ConfigBag)} but also putting the supplied values. */ |
| @Beta |
| public static ConfigBag newInstanceExtending(final ConfigBag configBag, Map<?,?> optionalAdditionalValues) { |
| return newInstanceExtending(configBag).putAll(optionalAdditionalValues); |
| } |
| |
| /** @deprecated since 0.7.0, not used; kept only for rebind compatibility where the inner class is used |
| * (now replaced by a static class above) */ |
| @Beta @Deprecated |
| private static ConfigBag newInstanceWithInnerClass(final ConfigBag configBag, Map<?,?> optionalAdditionalValues) { |
| return new ConfigBag() { |
| @Override |
| public void markUsed(String key) { |
| super.markUsed(key); |
| configBag.markUsed(key); |
| } |
| }.copy(configBag).putAll(optionalAdditionalValues); |
| } |
| |
| public ConfigBag() { |
| config = new LinkedHashMap<String,Object>(); |
| unusedConfig = new LinkedHashMap<String,Object>(); |
| live = false; |
| } |
| |
| private ConfigBag(Map<String,Object> storage) { |
| this.config = storage; |
| unusedConfig = new LinkedHashMap<String,Object>(); |
| live = true; |
| } |
| |
| public ConfigBag setDescription(String description) { |
| if (sealed) |
| throw new IllegalStateException("Cannot set description to '"+description+"': this config bag has been sealed and is now immutable."); |
| this.description = description; |
| return this; |
| } |
| |
| /** optional description used to provide context for operations */ |
| public String getDescription() { |
| return description; |
| } |
| |
| /** current values for all entries |
| * @return non-modifiable map of strings to object */ |
| public synchronized Map<String,Object> getAllConfig() { |
| return MutableMap.copyOf(config).asUnmodifiable(); |
| } |
| |
| /** current values for all entries in a map where the keys are converted to {@link ConfigKey} instances */ |
| public synchronized Map<ConfigKey<?>, ?> getAllConfigAsConfigKeyMap() { |
| Map<ConfigKey<?>,Object> result = MutableMap.of(); |
| for (Map.Entry<String,Object> entry: config.entrySet()) { |
| result.put(ConfigKeys.newConfigKey(Object.class, entry.getKey()), entry.getValue()); |
| } |
| return result; |
| } |
| |
| /** Returns the internal map containing the current values for all entries; |
| * for use where the caller wants to modify this directly and knows it is safe to do so |
| * <p> |
| * Accesses to the returned map must be synchronized on this bag if the |
| * thread-safe behaviour is required. */ |
| public Map<String,Object> getAllConfigMutable() { |
| if (live) { |
| // TODO sealed no longer works as before, because `config` is the backing storage map. |
| // Therefore returning it is dangerous! Even if we were to replace our field with an immutable copy, |
| // the underlying datagrid's map would still be modifiable. We need a way to switch the returned |
| // value's behaviour to sealable (i.e. wrapping the returned map). |
| return (sealed) ? MutableMap.copyOf(config).asUnmodifiable() : config; |
| } else { |
| return config; |
| } |
| } |
| |
| /** current values for all entries which have not yet been used |
| * @return non-modifiable map of strings to object */ |
| public synchronized Map<String,Object> getUnusedConfig() { |
| return MutableMap.copyOf(unusedConfig).asUnmodifiable(); |
| } |
| |
| /** Returns the internal map containing the current values for all entries which have not yet been used; |
| * for use where the caller wants to modify this directly and knows it is safe to do so |
| * <p> |
| * Accesses to the returned map must be synchronized on this bag if the |
| * thread-safe behaviour is required. */ |
| public Map<String,Object> getUnusedConfigMutable() { |
| return unusedConfig; |
| } |
| |
| public ConfigBag putAll(Map<?,?> addlConfig) { |
| if (addlConfig==null) return this; |
| for (Map.Entry<?,?> e: addlConfig.entrySet()) { |
| putAsStringKey(e.getKey(), e.getValue()); |
| } |
| return this; |
| } |
| |
| public ConfigBag putAll(ConfigBag addlConfig) { |
| if (addlConfig!=null) { |
| addlConfig.forEach((k,v)->{ |
| putStringKey(k, v, !addlConfig.unusedConfig.containsKey(k)); |
| }); |
| } |
| return this; |
| } |
| |
| public void forEach(BiConsumer<String,Object> action) { |
| getAllConfig().forEach(action); |
| } |
| |
| public <T> ConfigBag putIfAbsent(ConfigKey<T> key, T value) { |
| return putIfAbsent(MutableMap.of(key, value)); |
| } |
| |
| public ConfigBag putAsStringKeyIfAbsent(Object key, Object value) { |
| return putIfAbsent(MutableMap.of(key, value)); |
| } |
| |
| public synchronized ConfigBag putIfAbsent(Map<?, ?> propertiesToSet) { |
| if (propertiesToSet==null) |
| return this; |
| for (Map.Entry<?, ?> entry: propertiesToSet.entrySet()) { |
| Object key = entry.getKey(); |
| if (key instanceof HasConfigKey<?>) |
| key = ((HasConfigKey<?>)key).getConfigKey(); |
| if (key instanceof ConfigKey<?>) { |
| if (!containsKey((ConfigKey<?>)key)) |
| putAsStringKey(key, entry.getValue()); |
| } else if (key instanceof String) { |
| if (!containsKey((String)key)) |
| putAsStringKey(key, entry.getValue()); |
| } else { |
| logInvalidKey(key); |
| } |
| } |
| return this; |
| } |
| |
| public ConfigBag putIfAbsent(ConfigBag addlConfig) { |
| return putIfAbsent(addlConfig.getAllConfig()); |
| } |
| |
| |
| @SuppressWarnings("unchecked") |
| public <T> T put(ConfigKey<T> key, T value) { |
| return (T) putStringKey(key.getName(), value); |
| } |
| |
| public <T> ConfigBag putIfNotNull(ConfigKey<T> key, T value) { |
| if (value!=null) put(key, value); |
| return this; |
| } |
| |
| public <T> ConfigBag putIfAbsentAndNotNull(ConfigKey<T> key, T value) { |
| if (value!=null) putIfAbsent(key, value); |
| return this; |
| } |
| |
| /** as {@link #put(ConfigKey, Object)} but returning this ConfigBag for fluent-style coding */ |
| public <T> ConfigBag configure(ConfigKey<T> key, T value) { |
| putStringKey(key.getName(), value); |
| return this; |
| } |
| |
| public <T> ConfigBag configureStringKey(String key, T value) { |
| putStringKey(key, value); |
| return this; |
| } |
| |
| protected synchronized void putAsStringKey(Object key, Object value) { |
| if (key instanceof HasConfigKey<?>) key = ((HasConfigKey<?>)key).getConfigKey(); |
| if (key instanceof ConfigKey<?>) key = ((ConfigKey<?>)key).getName(); |
| if (key instanceof String) { |
| putStringKey((String)key, value); |
| } else { |
| logInvalidKey(key); |
| } |
| } |
| |
| protected void logInvalidKey(Object key) { |
| String message = (key == null ? "Invalid key 'null'" : "Invalid key type "+key.getClass().getCanonicalName()+" ("+key+")") + |
| " being used for configuration, ignoring"; |
| log.debug(message, new Throwable("Source of "+message)); |
| log.warn(message); |
| } |
| |
| /** recommended to use {@link #put(ConfigKey, Object)} but there are times |
| * (e.g. when copying a map) where we want to put a string key directly |
| */ |
| public synchronized Object putStringKey(String key, Object value) { |
| if (sealed) |
| throw new IllegalStateException("Cannot insert "+key+"="+value+": this config bag has been sealed and is now immutable."); |
| boolean isNew = !config.containsKey(key); |
| boolean isUsed = !isNew && !unusedConfig.containsKey(key); |
| Object old = config.put(key, value); |
| if (!isUsed) |
| unusedConfig.put(key, value); |
| return old; |
| } |
| protected synchronized Object putStringKey(String key, Object value, boolean valueBeingSetIsAlreadyUsed) { |
| if (!valueBeingSetIsAlreadyUsed) { |
| return putStringKey(key, value); |
| } |
| if (sealed) { |
| if (Objects.equal(value, config.get(key))) return value; |
| throw new IllegalStateException("Cannot insert " + key + "=" + value + ": this config bag has been sealed and is now immutable."); |
| } |
| Object old = config.put(key, value); |
| unusedConfig.remove(key); |
| return old; |
| } |
| public Object putStringKeyIfHasValue(String key, Maybe<?> value) { |
| if (value.isPresent()) |
| return putStringKey(key, value.get()); |
| return null; |
| } |
| public Object putStringKeyIfNotNull(String key, Object value) { |
| if (value!=null) |
| return putStringKey(key, value); |
| return null; |
| } |
| |
| public boolean containsKey(HasConfigKey<?> key) { |
| return containsKey(key.getConfigKey()); |
| } |
| |
| public boolean containsKey(ConfigKey<?> key) { |
| boolean found = containsKey(key.getName()); |
| if (!found) { |
| for (String deprecatedName : key.getDeprecatedNames()) { |
| found = containsKey(deprecatedName); |
| if (found) break; |
| } |
| } |
| return found; |
| } |
| |
| public synchronized boolean containsKey(String key) { |
| return config.containsKey(key); |
| } |
| |
| /** returns the value of this config key, falling back to its default (use containsKey to see whether it was contained); |
| * also marks it as having been used (use peek to prevent marking as used) |
| */ |
| public <T> T get(ConfigKey<T> key) { |
| return get(key, true); |
| } |
| |
| /** gets a value from a string-valued key or null; ConfigKey is preferred, but this is useful in some contexts (e.g. setting from flags) */ |
| public Object getStringKey(String key) { |
| return getStringKeyMaybe(key).orNull(); |
| } |
| /** gets a {@link Maybe}-wrapped value from a string-valued key; ConfigKey is preferred, but this is useful in some contexts (e.g. setting from flags) */ |
| public @Nonnull Maybe<Object> getStringKeyMaybe(String key) { |
| return getStringKeyMaybe(key, true); |
| } |
| |
| /** gets a {@link Maybe}-wrapped value from a key, inferring the type of that key (e.g. {@link ConfigKey} or {@link String}) */ |
| @Beta |
| public Maybe<Object> getObjKeyMaybe(Object key) { |
| if (key instanceof HasConfigKey<?>) key = ((HasConfigKey<?>)key).getConfigKey(); |
| if (key instanceof ConfigKey<?>) { |
| return getKeyMaybeUncoercedIfPresent((ConfigKey<?>)key, true); |
| } else if (key instanceof String) { |
| return getStringKeyMaybe((String)key, true); |
| } else { |
| logInvalidKey(key); |
| return Maybe.absent(); |
| } |
| } |
| |
| /** gets a {@link Maybe}-wrapped value from a key, inferring the type of that key (e.g. {@link ConfigKey} or {@link String}) */ |
| @Beta |
| private Maybe<Object> getRawObjKeyMaybe(Object key) { |
| if (key instanceof HasConfigKey<?>) key = ((HasConfigKey<?>)key).getConfigKey(); |
| if (key instanceof ConfigKey<?>) { |
| return this.getRawKeyMaybe((ConfigKey<?>)key, true); |
| } else if (key instanceof String) { |
| return this.getRawStringKeyMaybe((String)key, true); |
| } else { |
| logInvalidKey(key); |
| return Maybe.absent(); |
| } |
| } |
| |
| /** Puts into this bag the raw value for the given key in the given bag, if it was present. */ |
| @Beta |
| public <T> ConfigBag copyKey(ConfigBag source, ConfigKey<T> key) { |
| return copyKeyAs(source, key, key); |
| } |
| |
| /** Puts into this bag the raw value for the given key in the given bag, if it was present. */ |
| @Beta |
| public <T> ConfigBag copyKeys(ConfigBag source, ConfigKey<T> ...keys) { |
| for (ConfigKey<T> key : keys) { |
| copyKey(source, key); |
| } |
| return this; |
| } |
| |
| /** As {@link #copyKey(ConfigBag, ConfigKey)} but allowing a different key name to be used when writing here. */ |
| @Beta |
| @SuppressWarnings("unchecked") |
| public <T> ConfigBag copyKeyAs(ConfigBag source, ConfigKey<T> sourceKey, ConfigKey<T> targetKey) { |
| Maybe<Object> sourceValue = source.getRawKeyMaybe(sourceKey, true); |
| if (sourceValue.isPresent()) { |
| put(targetKey, (T) sourceValue.get()); |
| } |
| return this; |
| } |
| |
| /** like get, but without marking it as used */ |
| public <T> T peek(ConfigKey<T> key) { |
| return get(key, false); |
| } |
| |
| /** returns the first key in the list for which a value is explicitly set, then defaulting to defaulting value of preferred key */ |
| public synchronized <T> T getFirst(ConfigKey<T> preferredKey, ConfigKey<T> ...otherCurrentKeysInOrderOfPreference) { |
| if (containsKey(preferredKey)) |
| return get(preferredKey); |
| for (ConfigKey<T> key: otherCurrentKeysInOrderOfPreference) { |
| if (containsKey(key)) |
| return get(key); |
| } |
| return get(preferredKey); |
| } |
| |
| /** |
| * Convenience for @see #getWithDeprecation(ConfigKey[], ConfigKey...). |
| * |
| * @deprecated since 0.12.0; instead define deprecated names on key, see {@link ConfigKey#getDeprecatedNames()} |
| */ |
| @Deprecated |
| public Object getWithDeprecation(ConfigKey<?> key, ConfigKey<?> ...deprecatedKeys) { |
| return getWithDeprecation(new ConfigKey[] { key }, deprecatedKeys); |
| } |
| |
| /** |
| * Returns the value for the first key in the list for which a value is set, |
| * warning if any of the deprecated keys have a value which is different to that set on the first set current key |
| * (including warning if a deprecated key has a value but no current key does). |
| * |
| * @deprecated since 0.12.0; instead define deprecated names on key, see {@link ConfigKey#getDeprecatedNames()} |
| */ |
| @Deprecated |
| public synchronized Object getWithDeprecation(ConfigKey<?>[] currentKeysInOrderOfPreference, ConfigKey<?> ...deprecatedKeys) { |
| // Get preferred key (or null) |
| ConfigKey<?> preferredKeyProvidingValue = null; |
| Object result = null; |
| boolean found = false; |
| for (ConfigKey<?> key: currentKeysInOrderOfPreference) { |
| if (containsKey(key)) { |
| preferredKeyProvidingValue = key; |
| result = get(preferredKeyProvidingValue); |
| found = true; |
| break; |
| } |
| } |
| |
| // Check if any deprecated keys are set |
| ConfigKey<?> deprecatedKeyProvidingValue = null; |
| Object deprecatedResult = null; |
| boolean foundDeprecated = false; |
| for (ConfigKey<?> deprecatedKey: deprecatedKeys) { |
| Object x = null; |
| boolean foundX = false; |
| if (containsKey(deprecatedKey)) { |
| x = get(deprecatedKey); |
| foundX = true; |
| } |
| if (foundX) { |
| if (found) { |
| if (!Objects.equal(result, x)) { |
| log.warn("Conflicting value from deprecated key " +deprecatedKey+", value "+x+ |
| "; using preferred key "+preferredKeyProvidingValue+" value "+result); |
| } else { |
| log.info("Deprecated key " +deprecatedKey+" ignored; has same value as preferred key "+preferredKeyProvidingValue+" ("+result+")"); |
| } |
| } else if (foundDeprecated) { |
| if (!Objects.equal(result, x)) { |
| log.warn("Conflicting values from deprecated keys: using " +deprecatedKeyProvidingValue+" instead of "+deprecatedKey+ |
| " (value "+deprecatedResult+" instead of "+x+")"); |
| } else { |
| log.info("Deprecated key " +deprecatedKey+" ignored; has same value as other deprecated key "+preferredKeyProvidingValue+" ("+deprecatedResult+")"); |
| } |
| } else { |
| // new value, from deprecated key |
| log.warn("Deprecated key " +deprecatedKey+" detected (supplying value "+x+"), "+ |
| "; recommend changing to preferred key '"+currentKeysInOrderOfPreference[0]+"'; this will not be supported in future versions"); |
| deprecatedResult = x; |
| deprecatedKeyProvidingValue = deprecatedKey; |
| foundDeprecated = true; |
| } |
| } |
| } |
| |
| if (found) { |
| return result; |
| } else if (foundDeprecated) { |
| return deprecatedResult; |
| } else { |
| return currentKeysInOrderOfPreference[0].getDefaultValue(); |
| } |
| } |
| |
| protected <T> T get(ConfigKey<T> key, boolean markUsed) { |
| // TODO for now, no evaluation -- maps / closure content / other smart (self-extracting) keys are NOT supported |
| // (need a clean way to inject that behaviour, as well as desired TypeCoercions) |
| // this method, and the coercion, is not synchronized, nor does it need to be, because the "get" is synchronized. |
| Maybe<Object> val = getKeyMaybeUncoercedIfPresent(key, markUsed); |
| return coerceFirstNonNullKeyValue(key, val.orNull()); |
| } |
| |
| /** If the key is set, return the type-correct (coerced) value, but not any default; otherwise returns absent */ |
| public <T> Maybe<T> ifPresent(ConfigKey<T> key) { |
| Maybe<Object> val = getKeyMaybeUncoercedIfPresent(key, true); |
| if (val.isAbsent()) return ((Maybe<T>)val); |
| return Maybe.of(coerceFirstNonNullKeyValue(key, val.get())); |
| } |
| |
| /** returns the first non-null value to be the type indicated by the key, or the keys default value if no non-null values are supplied */ |
| public static <T> T coerceFirstNonNullKeyValue(ConfigKey<T> key, Object ...values) { |
| for (Object o: values) { |
| if (o != null) { |
| try { |
| return TypeCoercions.coerce(o, key.getTypeToken()); |
| } catch (Exception e) { |
| throw Exceptions.propagate("Error resolving "+key.getName()+" value "+o+" as "+key.getTypeToken()+": "+e, e); |
| } |
| } |
| } |
| try { |
| return TypeCoercions.coerce(key.getDefaultValue(), key.getTypeToken()); |
| } catch (Exception e) { |
| throw Exceptions.propagate("Error resolving "+key.getName()+" default value "+key.getDefaultValue()+" as "+key.getTypeToken()+": "+e, e); |
| } |
| } |
| |
| protected Object getStringKey(String key, boolean markUsed) { |
| return getStringKeyMaybe(key, markUsed).orNull(); |
| } |
| |
| |
| private synchronized Maybe<Object> getKeyMaybeInternal(ConfigKey<?> key, Function<String, Maybe<Object>> getKey) { |
| Maybe<Object> val = getKey.apply(key.getName()); |
| |
| String firstDeprecatedName = null; |
| Maybe<Object> firstDeprecatedVal = null; |
| for (String deprecatedName : key.getDeprecatedNames()) { |
| Maybe<Object> deprecatedVal = getKey.apply(deprecatedName); |
| if (deprecatedVal.isPresent()) { |
| if (val.isPresent()) { |
| if (!Objects.equal(val.get(), deprecatedVal.get())) { |
| log.warn("Conflicting value for key "+key+" from deprecated name '"+deprecatedName+"'; " |
| + "using value from preferred name "+key.getName()); |
| } else { |
| log.warn("Duplicate value for key "+key+" from deprecated name '"+deprecatedName+"'; " |
| + "using same value from preferred name "+key.getName()); |
| } |
| } else if (firstDeprecatedVal != null && firstDeprecatedVal.isPresent()) { |
| if (!Objects.equal(firstDeprecatedVal.get(), deprecatedVal.get())) { |
| log.warn("Conflicting value for key "+key+" from deprecated name '"+deprecatedName+"'; " |
| + "using earlier deprecated name "+firstDeprecatedName); |
| } else { |
| log.warn("Duplicate value for key "+key+" from deprecated name '"+deprecatedName+"'; " |
| + "using same value from earlier depreated name "+key.getName()); |
| } |
| } else { |
| // new value, from deprecated name |
| log.warn("Value for key "+key+" found with deprecated name '"+deprecatedName+"'; " |
| + "recommend changing to preferred name '"+key.getName()+"'; this will not be supported in future versions"); |
| firstDeprecatedName = deprecatedName; |
| firstDeprecatedVal = deprecatedVal; |
| } |
| } |
| } |
| |
| return val.isPresent() ? val : (firstDeprecatedVal != null && firstDeprecatedVal.isPresent() ? firstDeprecatedVal : val); |
| } |
| |
| |
| private synchronized Maybe<Object> getRawKeyMaybe(ConfigKey<?> key, boolean markUsed) { |
| return getKeyMaybeInternal(key, name -> getRawStringKeyMaybe(name, markUsed)); |
| } |
| |
| /** |
| * @return Unresolved configuration value. May be overridden to provide resolution - @see {@link ResolvingConfigBag#getStringKeyMaybe(String, boolean)} |
| */ |
| protected synchronized Maybe<Object> getKeyMaybeUncoercedIfPresent(ConfigKey<?> key, boolean markUsed) { |
| return getKeyMaybeInternal(key, name -> getStringKeyMaybe(name, markUsed)); |
| } |
| |
| private synchronized Maybe<Object> getRawStringKeyMaybe(String key, boolean markUsed) { |
| if (config.containsKey(key)) { |
| if (markUsed) markUsed(key); |
| return Maybe.of(config.get(key)); |
| } |
| return Maybe.absent(); |
| } |
| |
| /** |
| * @return Unresolved configuration value. May be overridden to provide resolution - @see {@link ResolvingConfigBag#getStringKeyMaybe(String, boolean)} |
| */ |
| protected synchronized Maybe<Object> getStringKeyMaybe(String key, boolean markUsed) { |
| return getRawStringKeyMaybe(key, markUsed); |
| } |
| |
| /** indicates that a string key in the config map has been accessed */ |
| public synchronized void markUsed(String key) { |
| unusedConfig.remove(key); |
| } |
| |
| public synchronized void clear() { |
| if (sealed) |
| throw new IllegalStateException("Cannot clear this config bag has been sealed and is now immutable."); |
| config.clear(); |
| unusedConfig.clear(); |
| } |
| |
| public ConfigBag removeAll(ConfigKey<?> ...keys) { |
| for (ConfigKey<?> key: keys) remove(key); |
| return this; |
| } |
| |
| public synchronized void remove(ConfigKey<?> key) { |
| remove(key.getName()); |
| } |
| |
| public ConfigBag removeAll(Iterable<String> keys) { |
| for (String key: keys) remove(key); |
| return this; |
| } |
| |
| public synchronized void remove(String key) { |
| if (sealed) |
| throw new IllegalStateException("Cannot remove "+key+": this config bag has been sealed and is now immutable."); |
| config.remove(key); |
| unusedConfig.remove(key); |
| } |
| |
| public ConfigBag copy(ConfigBag other) { |
| // ensure locks are taken in a canonical order to prevent deadlock |
| if (other==null) { |
| synchronized (this) { |
| return copyWhileSynched(other); |
| } |
| } |
| if (System.identityHashCode(other) < System.identityHashCode(this)) { |
| synchronized (other) { |
| synchronized (this) { |
| return copyWhileSynched(other); |
| } |
| } |
| } else { |
| synchronized (this) { |
| synchronized (other) { |
| return copyWhileSynched(other); |
| } |
| } |
| } |
| } |
| |
| protected ConfigBag copyWhileSynched(ConfigBag other) { |
| if (sealed) |
| throw new IllegalStateException("Cannot copy "+other+" to "+this+": this config bag has been sealed and is now immutable."); |
| if (other!=null) { |
| putAll(other.getAllConfig()); |
| markAll(Sets.difference(other.getAllConfig().keySet(), other.getUnusedConfig().keySet())); |
| setDescription(other.getDescription()); |
| } |
| return this; |
| } |
| |
| public synchronized int size() { |
| return config.size(); |
| } |
| |
| public synchronized boolean isEmpty() { |
| return config.isEmpty(); |
| } |
| |
| public ConfigBag markAll(Iterable<String> usedFlags) { |
| for (String flag: usedFlags) |
| markUsed(flag); |
| return this; |
| } |
| |
| public synchronized boolean isUnused(ConfigKey<?> key) { |
| return unusedConfig.containsKey(key.getName()); |
| } |
| |
| /** makes this config bag immutable; any attempts to change subsequently |
| * (apart from marking fields as used) will throw an exception |
| * <p> |
| * copies will be unsealed however |
| * <p> |
| * returns this for convenience (fluent usage) */ |
| public ConfigBag seal() { |
| sealed = true; |
| if (live) { |
| // TODO How to ensure sealed?! |
| } else { |
| config = getAllConfig(); |
| } |
| return this; |
| } |
| |
| /** |
| * Whether this config bag is "sealed" (i.e. whether no more modifications can be made to it). |
| * This method is for information only - it should not be overridden to try to change the |
| * semantics, as internal methods access the (private) field directly. |
| */ |
| protected final boolean isSealed() { |
| return sealed; |
| } |
| |
| // TODO why have both this and mutable |
| /** @see #getAllConfigMutable() */ |
| public Map<String, Object> getAllConfigRaw() { |
| return getAllConfigMutable(); |
| } |
| |
| @Override |
| public String toString() { |
| return JavaClassNames.simpleClassName(this)+"["+getAllConfigRaw()+"]"; |
| } |
| |
| } |