using new config inheritance evaluation, inserted in one place (entities)

Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/29706561
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/29706561
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/29706561

Branch: refs/heads/master
Commit: 29706561bfba35953deb420d478ae0cb9035863a
Parents: 189677d
Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com>
Authored: Fri Sep 16 09:36:37 2016 +0100
Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com>
Committed: Tue Sep 20 08:48:27 2016 +0100

----------------------------------------------------------------------
 .../core/config/BasicConfigInheritance.java     |  66 ++++++
 .../core/entity/internal/EntityConfigMap.java   | 150 +++++++-------
 .../brooklyn/core/objs/BasicSpecParameter.java  |   3 +-
 .../brooklyn/config/ConfigInheritance.java      | 207 +++++++++++++++----
 4 files changed, 311 insertions(+), 115 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/29706561/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
 
b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
new file mode 100644
index 0000000..a238809
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
@@ -0,0 +1,66 @@
+/*
+ * 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.core.config;
+
+import java.util.Iterator;
+
+import org.apache.brooklyn.config.ConfigInheritance;
+import org.apache.brooklyn.config.ConfigKey;
+
+public class BasicConfigInheritance implements ConfigInheritance {
+
+    private static final long serialVersionUID = -5916548049057961051L;
+
+    // TODO javadoc
+    public static BasicConfigInheritance NOT_REINHERITED = new 
BasicConfigInheritance(false,"overwrite",false);
+    public static BasicConfigInheritance NEVER_INHERITED = new 
BasicConfigInheritance(false,"overwrite",true);
+    public static BasicConfigInheritance OVERWRITE = new 
BasicConfigInheritance(true,"overwrite",false);
+    public static BasicConfigInheritance DEEP_MERGE = new 
BasicConfigInheritance(true,"deep_merge",false);
+    
+//    reinheritable? true/false; if false, children/descendants/inheritors 
will never see it; default true
+    protected final boolean isReinherited;
+//    conflict-resolution-strategy? overwrite or deep_merge or null; default 
null meaning caller supplies
+    protected final String conflictResolutionStrategy;
+//    use-local-default-value? true/false; if true, overwrite above means 
"always ignore"; default false
+    protected final boolean useLocalDefaultValue;
+
+    protected BasicConfigInheritance(boolean isReinherited, String 
conflictResolutionStrategy, boolean useLocalDefaultValue) {
+        super();
+        this.isReinherited = isReinherited;
+        this.conflictResolutionStrategy = conflictResolutionStrategy;
+        this.useLocalDefaultValue = useLocalDefaultValue;
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public InheritanceMode isInherited(ConfigKey<?> key, Object from, Object 
to) {
+        return null;
+    }
+    
+    @Override
+    public <T> ContainerAndValue<T> resolveInheriting(
+            Iterator<ContainerAndKeyValue<T>> containerAndDataThroughAncestors,
+            ConfigInheritanceContext context) {
+//        XXX;
+        return null;
+    }
+
+}
+
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/29706561/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
index 25d1561..ee31779 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
@@ -22,22 +22,21 @@ import static 
com.google.common.base.Preconditions.checkNotNull;
 import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.elvis;
 
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
+import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ExecutionContext;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.config.ConfigInheritance;
+import org.apache.brooklyn.config.ConfigInheritance.ContainerAndKeyValue;
+import org.apache.brooklyn.config.ConfigInheritance.ContainerAndValue;
 import org.apache.brooklyn.config.ConfigInheritance.InheritanceMode;
 import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys.InheritanceContext;
 import org.apache.brooklyn.core.config.Sanitizer;
 import org.apache.brooklyn.core.config.StructuredConfigKey;
 import org.apache.brooklyn.core.config.internal.AbstractConfigMapImpl;
@@ -50,6 +49,12 @@ import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
 public class EntityConfigMap extends AbstractConfigMapImpl {
 
@@ -94,15 +99,6 @@ public class EntityConfigMap extends AbstractConfigMapImpl {
         //           but that example doesn't have a default...
         ConfigKey<T> ownKey = entity!=null ? 
(ConfigKey<T>)elvis(entity.getEntityType().getConfigKey(key.getName()), key) : 
key;
         
-        // TODO Confirm desired behaviour: I switched this around to get 
ownKey.getParentInheritance first.
-        ConfigInheritance inheritance = ownKey.getParentInheritance();
-        if (inheritance==null) inheritance = key.getParentInheritance();
-        if (inheritance==null) {
-            // TODO we could warn by introducing a temporary 
"ALWAYS_BUT_WARNING" instance
-            inheritance = getDefaultParentInheritance(); 
-        }
-        InheritanceMode parentInheritanceMode = 
inheritance.isInherited(ownKey, entity.getParent(), entity);
-        
         // TODO We're notifying of config-changed because currently 
persistence needs to know when the
         // attributeWhenReady is complete (so it can persist the result).
         // Long term, we'll just persist tasks properly so the call to 
onConfigChanged will go!
@@ -110,7 +106,8 @@ public class EntityConfigMap extends AbstractConfigMapImpl {
         // Don't use groovy truth: if the set value is e.g. 0, then would 
ignore set value and return default!
         if (ownKey instanceof ConfigKeySelfExtracting) {
             Object rawval = ownConfig.get(key);
-            Maybe<T> result = 
getConfigImpl((ConfigKeySelfExtracting<T>)ownKey, parentInheritanceMode);
+            // TODO this no longer looks at key's inheritance
+            Maybe<T> result = 
getConfigImpl((ConfigKeySelfExtracting<T>)ownKey);
 
             if (rawval instanceof Task) {
                 
entity.getManagementSupport().getEntityChangeListener().onConfigChanged(key);
@@ -125,8 +122,8 @@ public class EntityConfigMap extends AbstractConfigMapImpl {
     }
     
     @SuppressWarnings("unchecked")
-    private <T> Maybe<T> getConfigImpl(ConfigKeySelfExtracting<T> key, 
InheritanceMode parentInheritance) {
-        ExecutionContext exec = entity.getExecutionContext();
+    private <T> Maybe<T> getConfigImpl(final ConfigKeySelfExtracting<T> key) {
+        final ExecutionContext exec = entity.getExecutionContext();
         Maybe<T> ownValue;
         Maybe<T> parentValue;
 
@@ -145,76 +142,75 @@ public class EntityConfigMap extends 
AbstractConfigMapImpl {
         } else {
             ownValue = Maybe.<T>absent();
         }
+        final Maybe<T> ownValueF = ownValue;
 
-        // Get the parent-inheritance value (but only if we'll need it)
-        switch (parentInheritance) {
-        case IF_NO_EXPLICIT_VALUE:
-            if (ownValue.isAbsent()) {
-                if (((ConfigKeySelfExtracting<T>)key).isSet(inheritedConfig)) {
-                    parentValue = 
Maybe.of(((ConfigKeySelfExtracting<T>)key).extractValue(inheritedConfig, exec));
-                } else if (inheritedConfigBag.containsKey(key)) {
-                    parentValue = Maybe.of(inheritedConfigBag.get(key));
-                } else {
-                    parentValue = Maybe.absent();
-                }
-            } else {
-                parentValue = Maybe.absent();
+        ContainerAndValue<T> result = 
getDefaultRuntimeInheritance().resolveInheriting(new 
Iterator<ContainerAndKeyValue<T>>() {
+            int count = 0;
+            @Override
+            public boolean hasNext() {
+                return count < 2;
             }
-            break;
-        case DEEP_MERGE:
-            if (((ConfigKeySelfExtracting<T>)key).isSet(inheritedConfig)) {
-                parentValue = 
Maybe.of(((ConfigKeySelfExtracting<T>)key).extractValue(inheritedConfig, exec));
-            } else if (inheritedConfigBag.containsKey(key)) {
-                parentValue = Maybe.of(inheritedConfigBag.get(key));
-            } else {
-                parentValue = Maybe.absent();
+            @Override
+            public ContainerAndKeyValue<T> next() {
+                if (count >= 2) throw new NoSuchElementException();
+                final boolean isLookingAtInheritedBag = (count==1);
+                try {
+                    return new ContainerAndKeyValue<T>() {
+                        @Override
+                        public Object getContainer() {
+                            // TODO the current inheritedConfigBag is not good 
enough to detect the ancestor container
+                            return !isLookingAtInheritedBag ? entity : 
entity.getParent();
+                        }
+                        @Override
+                        public T getValue() {
+                            return peekValue().orNull();
+                        }
+                        protected Maybe<T> peekValue() {
+                            if (!isLookingAtInheritedBag) return ownValueF;
+                            if 
(((ConfigKeySelfExtracting<T>)key).isSet(inheritedConfig)) {
+                                return Maybe.of( 
((ConfigKeySelfExtracting<T>)key).extractValue(inheritedConfig, exec) );
+                            } else if (inheritedConfigBag.containsKey(key)) {
+                                return Maybe.of(inheritedConfigBag.get(key));
+                            } else {
+                                return Maybe.absent();
+                            }
+                        }
+    
+                        @Override
+                        public boolean isValueSet() {
+                            return peekValue().isPresent();
+                        }
+    
+                        @Override
+                        public ConfigKey<T> getKey() {
+                            return key;
+                        }
+    
+                        @Override
+                        public T getDefaultValue() {
+                            return key.getDefaultValue();
+                        }
+                    };
+                } finally {
+                    count++;
+                }
             }
-            break;
-        case NONE:
-            parentValue = Maybe.absent();
-            break;
-        default:
-            throw new IllegalStateException("Unsupported parent-inheritance 
mode for "+key.getName()+": "+parentInheritance);
-        }
-
-        // Merge or override, as appropriate
-        switch (parentInheritance) {
-        case IF_NO_EXPLICIT_VALUE:
-            return ownValue.isPresent() ? ownValue : parentValue;
-        case DEEP_MERGE:
-            return (Maybe<T>) deepMerge(ownValue, parentValue, key);
-        case NONE:
-            return ownValue;
-        default:
-            throw new IllegalStateException("Unsupported parent-inheritance 
mode for "+key.getName()+": "+parentInheritance);
-        }
+            @Override public void remove() { throw new 
UnsupportedOperationException(); }
+        }, InheritanceContext.RUNTIME_MANAGEMENT);
+        
+        if (result.isValueSet()) return Maybe.of(result.getValue());
+        return Maybe.absent();
     }
     
-    private <T> Maybe<?> deepMerge(Maybe<? extends T> val1, Maybe<? extends T> 
val2, ConfigKey<?> keyForLogging) {
-        if (val2.isAbsent() || val2.isNull()) {
-            return val1;
-        } else if (val1.isAbsent()) {
-            return val2;
-        } else if (val1.isNull()) {
-            return val1; // an explicit null means an override; don't merge
-        } else if (val1.get() instanceof Map && val2.get() instanceof Map) {
-            return 
Maybe.of(CollectionMerger.builder().build().merge((Map<?,?>)val1.get(), 
(Map<?,?>)val2.get()));
-        } else {
-            // cannot merge; just return val1
-            LOG.debug("Cannot merge values for "+keyForLogging.getName()+", 
because values are not maps: "+val1.get().getClass()+", and 
"+val2.get().getClass());
-            return val1;
-        }
-    }
-
     private <T> boolean isInherited(ConfigKey<T> key) {
         return isInherited(key, key.getParentInheritance());
     }
     private <T> boolean isInherited(ConfigKey<T> key, ConfigInheritance 
inheritance) {
-        if (inheritance==null) inheritance = getDefaultParentInheritance();
+        if (inheritance==null) inheritance = getDefaultRuntimeInheritance();
         InheritanceMode mode = inheritance.isInherited(key, 
entity.getParent(), entity);
         return mode != null && mode != InheritanceMode.NONE;
     }
-    private ConfigInheritance getDefaultParentInheritance() {
+    private ConfigInheritance getDefaultRuntimeInheritance() {
         return ConfigInheritance.ALWAYS; 
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/29706561/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java 
b/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
index dc5ba3e..db0b7c3 100644
--- a/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
+++ b/core/src/main/java/org/apache/brooklyn/core/objs/BasicSpecParameter.java
@@ -311,7 +311,8 @@ public class BasicSpecParameter<T> implements 
SpecParameter<T>{
         
         private static ConfigInheritance parseInheritance(Object obj, 
BrooklynClassLoadingContext loader) {
             if (obj == null || obj instanceof String) {
-                return ConfigInheritance.fromString((String)obj);
+                // TODO
+                return ConfigInheritance.Legacy.fromString((String)obj);
             } else {
                 throw new IllegalArgumentException ("The config-inheritance '" 
+ obj + "' for a catalog input is invalid format - string supported");
             }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/29706561/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
----------------------------------------------------------------------
diff --git 
a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java 
b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
index 4bdbdda..a07a9d2 100644
--- 
a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
+++ 
b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
@@ -19,66 +19,199 @@
 package org.apache.brooklyn.config;
 
 import java.io.Serializable;
+import java.util.Iterator;
+import java.util.Map;
 
+import org.apache.brooklyn.util.collections.CollectionMerger;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 
 import com.google.common.annotations.Beta;
 
 @SuppressWarnings("serial")
-public abstract class ConfigInheritance implements Serializable {
+public interface ConfigInheritance extends Serializable {
     
     /** marker interface for inheritance contexts, for keys which can define 
one or more inheritance patterns */
     public interface ConfigInheritanceContext {}
 
+    /** @deprecated since 0.10.0 see implementations of this interface */ 
@Deprecated
     @Beta
     public enum InheritanceMode {
         NONE,
         IF_NO_EXPLICIT_VALUE,
         DEEP_MERGE
     }
+    
+    /** @deprecated since 0.10.0 see implementations of this interface */ 
@Deprecated
+    public static final ConfigInheritance NONE = new Legacy.None();
+    /** @deprecated since 0.10.0 see implementations of this interface */ 
@Deprecated
+    public static final ConfigInheritance ALWAYS = new Legacy.Always();
+    /** @deprecated since 0.10.0 see implementations of this interface */ 
@Deprecated
+    public static final ConfigInheritance DEEP_MERGE = new Legacy.Merged();
+    
+    @Deprecated
+    InheritanceMode isInherited(ConfigKey<?> key, Object from, Object to);
 
-    public static final ConfigInheritance NONE = new None();
-    public static final ConfigInheritance ALWAYS = new Always();
-    public static final ConfigInheritance DEEP_MERGE = new Merged();
+    interface ContainerAndValue<T> {
+        Object getContainer();
+        T getValue();
+        /** if false, the contents of {@link #getValue()} will have come from 
the default */
+        boolean isValueSet();
+    }
     
-    public static ConfigInheritance fromString(String val) {
-        if (Strings.isBlank(val)) return null;
-        switch (val.toLowerCase().trim()) {
-        case "none":
-            return NONE;
-        case "always": 
-            return ALWAYS;
-        case "deepmerge" :
-        case "deep_merge" :
-            return DEEP_MERGE;
-        default:
-            throw new IllegalArgumentException("Invalid config-inheritance 
'"+val+"' (legal values are none, always or merge)");
-        }
+    interface ContainerAndKeyValue<T> extends ContainerAndValue<T> {
+        ConfigKey<T> getKey();
+        T getDefaultValue();
     }
     
-    private ConfigInheritance() {}
+    /** 
+     * given an iterable of the config containers (eg an entity) and 
associated data,
+     * with the first entry being the container of immediate interest 
+     * and the subsequent nodes being the ancestors,
+     * this finds the value set for a config key after all inheritance 
strategies are applied,
+     * for instance merging a map throughout a container hierarchy, 
+     * or traversing up until a non-reinheritable key definition is found and 
then returning the default value of the key 
+     * <p>
+     * this uses an interface on the input so that:
+     * - the caller can supply the hierarchy
+     * - hierarchy is only traversed as far as needed
+     * - caller can do full resolution as required for local values
+     * <p>
+     * this returns in interface so that caller can get the value, 
+     * and if needed also find the container where the key is defined.
+     * that can be useful for when the value needs to be further resolved,
+     * e.g. a DSL function or a URL. the returned value may be evaluated 
lazily,
+     * i.e. the actual traversal and evaluation may be deferred until a method 
on
+     * the returned object is invoked.
+     * <p>
+     * this object is taken as the default inheritance and used if no 
inheritance is
+     * defined on the key.
+     * <p>
+     * so that the caller can determine if a key/value is to be exported to 
children from a container,
+     * this method should accept an iterable whose first entry has a null 
container
+     * and whose second entry gives the container, key, and potential value to 
be exported.
+     * if null is returned the caller knows nothing is to be exported to 
children.
+     */
+    <T> ContainerAndValue<T> resolveInheriting(
+        Iterator<ContainerAndKeyValue<T>> containerAndDataThroughAncestors,
+        ConfigInheritanceContext context);
     
-    @Beta
-    public abstract InheritanceMode isInherited(ConfigKey<?> key, Object from, 
Object to);
-
-    private static class Always extends ConfigInheritance {
-        @Override
-        public InheritanceMode isInherited(ConfigKey<?> key, Object from, 
Object to) {
-            return InheritanceMode.IF_NO_EXPLICIT_VALUE;
+    /** @deprecated since 0.10.0 see implementations of this interface */ 
@Deprecated
+    public static class Legacy {
+        public static ConfigInheritance fromString(String val) {
+            if (Strings.isBlank(val)) return null;
+            switch (val.toLowerCase().trim()) {
+            case "none":
+                return NONE;
+            case "always": 
+                return ALWAYS;
+            case "deepmerge" :
+            case "deep_merge" :
+                return DEEP_MERGE;
+            default:
+                throw new IllegalArgumentException("Invalid config-inheritance 
'"+val+"' (legal values are none, always or merge)");
+            }
         }
-    }
-
-    private static class None extends ConfigInheritance {
-        @Override
-        public InheritanceMode isInherited(ConfigKey<?> key, Object from, 
Object to) {
-            return InheritanceMode.NONE;
+        private abstract static class LegacyAbstractConversion implements 
ConfigInheritance {
+            private static class Result<T> implements ContainerAndValue<T> {
+                Object container = null;
+                T value = null;
+                boolean isValueSet = false;
+                @Override public Object getContainer() { return container; }
+                @Override public T getValue() { return value; }
+                @Override public boolean isValueSet() { return isValueSet; }
+            }
+            @Override
+            public <T> ContainerAndValue<T> resolveInheriting(
+                    Iterator<ContainerAndKeyValue<T>> containerAndLocalValues, 
+                    ConfigInheritanceContext context) {
+                if (containerAndLocalValues.hasNext()) {
+                    ContainerAndKeyValue<T> c = containerAndLocalValues.next();
+                    if (c==null) return 
resolveInheriting(containerAndLocalValues, context);
+                    
+                    ConfigKey<T> key = c.getKey();
+                    ConfigInheritance ci = null;
+                    if (key!=null) ci = key.getInheritanceByContext(context);
+                    if (ci==null) ci = this;
+                    
+                    if (getMode()==InheritanceMode.NONE) {
+                        // don't inherit, fall through to below
+                    } else if (!c.isValueSet()) {
+                        // no value here, try to inherit
+                        ContainerAndValue<T> ri = 
ci.resolveInheriting(containerAndLocalValues, context);
+                        if (ri.isValueSet()) {
+                            // value found, return it
+                            return ri;
+                        }
+                        // else no inherited, fall through to below
+                    } else {
+                        if (getMode()==InheritanceMode.IF_NO_EXPLICIT_VALUE) {
+                            // don't inherit, fall through to below
+                        } else {
+                            // merging
+                            Maybe<?> mr = deepMerge(asMaybe(c), 
+                                
asMaybe(ci.resolveInheriting(containerAndLocalValues, context)));
+                            if (mr.isPresent()) {
+                                Result<T> r = new Result<T>();
+                                r.container = c.getContainer();
+                                r.isValueSet = true;
+                                @SuppressWarnings("unchecked")
+                                T vt = (T) mr.get();
+                                r.value = vt;
+                                return r;
+                            }
+                        }
+                    }
+                    Result<T> r = new Result<T>();
+                    r.container = c.getContainer();
+                    r.isValueSet = c.isValueSet();
+                    r.value = r.isValueSet ? c.getValue() : 
c.getDefaultValue();
+                    return r;
+                }
+                return new Result<T>();
+            }
+            @Override
+            public InheritanceMode isInherited(ConfigKey<?> key, Object from, 
Object to) {
+                return getMode();
+            }
+            protected abstract InheritanceMode getMode();
+            private static <T> Maybe<T> asMaybe(ContainerAndValue<T> cv) {
+                if (cv.isValueSet()) return Maybe.of(cv.getValue());
+                return Maybe.absent();
+            }
+            private static <T> Maybe<?> deepMerge(Maybe<? extends T> val1, 
Maybe<? extends T> val2) {
+                if (val2.isAbsent() || val2.isNull()) {
+                    return val1;
+                } else if (val1.isAbsent()) {
+                    return val2;
+                } else if (val1.isNull()) {
+                    return val1; // an explicit null means an override; don't 
merge
+                } else if (val1.get() instanceof Map && val2.get() instanceof 
Map) {
+                    return 
Maybe.of(CollectionMerger.builder().build().merge((Map<?,?>)val1.get(), 
(Map<?,?>)val2.get()));
+                } else {
+                    // cannot merge; just return val1
+                    return val1;
+                }
+            }
         }
-    }
-    
-    private static class Merged extends ConfigInheritance {
-        @Override
-        public InheritanceMode isInherited(ConfigKey<?> key, Object from, 
Object to) {
-            return InheritanceMode.DEEP_MERGE;
+        private static class Always extends LegacyAbstractConversion {
+            @Override
+            public InheritanceMode getMode() {
+                return InheritanceMode.IF_NO_EXPLICIT_VALUE;
+            }
+        }
+        private static class None extends LegacyAbstractConversion {
+            @Override
+            public InheritanceMode getMode() {
+                return InheritanceMode.NONE;
+            }
+        }
+        private static class Merged extends LegacyAbstractConversion {
+            @Override
+            public InheritanceMode getMode() {
+                return InheritanceMode.DEEP_MERGE;
+            }
         }
     }
+    
 }

Reply via email to