You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@sling.apache.org by GitBox <gi...@apache.org> on 2018/11/21 09:53:53 UTC

[GitHub] bosschaert closed pull request #9: SLING-8104 Avoid magic when merging features

bosschaert closed pull request #9: SLING-8104 Avoid magic when merging features
URL: https://github.com/apache/sling-org-apache-sling-feature/pull/9
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderContext.java b/src/main/java/org/apache/sling/feature/builder/BuilderContext.java
index 1dd100c..61acba5 100644
--- a/src/main/java/org/apache/sling/feature/builder/BuilderContext.java
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderContext.java
@@ -29,10 +29,6 @@
  */
 public class BuilderContext {
 
-    enum ArtifactMergeAlgorithm {
-        LATEST, HIGHEST
-    }
-
     /** The required feature provider */
     private final FeatureProvider provider;
 
@@ -42,10 +38,10 @@
     private final Map<String, Map<String,String>> extensionConfiguration = new HashMap<>();
     private final List<MergeHandler> mergeExtensions = new ArrayList<>();
     private final List<PostProcessHandler> postProcessExtensions = new ArrayList<>();
+    private final List<String> artifactsOverrides = new ArrayList<>();
     private final Map<String,String> variables = new HashMap<>();
     private final Map<String,String> frameworkProperties = new HashMap<>();
 
-    private ArtifactMergeAlgorithm merge = ArtifactMergeAlgorithm.HIGHEST;
 
     /**
      * Create a new context
@@ -72,27 +68,38 @@ public BuilderContext setArtifactProvider(final ArtifactProvider ap) {
     }
 
     /**
-     * Add overwrites for the variables
+     * Add overrides for the variables
      *
-     * @param vars The overwrites
+     * @param vars The overrides
      * @return The builder context
      */
-    public BuilderContext addVariablesOverwrites(final Map<String,String> vars) {
+    public BuilderContext addVariablesOverrides(final Map<String,String> vars) {
         this.variables.putAll(vars);
         return this;
     }
 
     /**
-     * Add overwrites for the framework properties
+     * Add overrides for the framework properties
      *
-     * @param props The overwrites
+     * @param props The overrides
      * @return The builder context
      */
-    public BuilderContext addFrameworkPropertiesOverwrites(final Map<String,String> props) {
+    public BuilderContext addFrameworkPropertiesOverrides(final Map<String,String> props) {
         this.frameworkProperties.putAll(props);
         return this;
     }
 
+    /**
+     * Add overrides for artifact clashes
+     *
+     * @param overrides The overrides
+     * @return The builder context
+     */
+    public BuilderContext addArtifactsOverrides(final List<String> overrides) {
+        this.artifactsOverrides.addAll(overrides);
+        return this;
+    }
+
     /**
      * Add merge extensions
      *
@@ -115,17 +122,6 @@ public BuilderContext addPostProcessExtensions(final PostProcessHandler... exten
         return this;
     }
 
-    /**
-     * Set the merge algorithm
-     *
-     * @param alg The algorithm
-     * @return The builder context
-     */
-    public BuilderContext setMergeAlgorithm(final ArtifactMergeAlgorithm alg) {
-        this.merge = alg;
-        return this;
-    }
-
     /**
      * Set a handler configuration
      *
@@ -152,11 +148,15 @@ ArtifactProvider getArtifactProvider() {
         return this.artifactProvider;
     }
 
-    Map<String,String> getVariablesOverwrites() {
-        return  this.variables;
+    List<String> getArtifactOverrides() {
+        return this.artifactsOverrides;
+    }
+
+    Map<String,String> getVariablesOverrides() {
+        return this.variables;
     }
 
-    Map<String,String> getFrameworkPropertiesOverwrites() {
+    Map<String,String> getFrameworkPropertiesOverrides() {
         return this.frameworkProperties;
     }
 
@@ -168,10 +168,6 @@ FeatureProvider getFeatureProvider() {
         return this.provider;
     }
 
-    ArtifactMergeAlgorithm getMergeAlgorithm() {
-        return this.merge;
-    }
-
     /**
      * Get the list of merge extensions
      * @return The list of merge extensions
@@ -196,11 +192,11 @@ ArtifactMergeAlgorithm getMergeAlgorithm() {
     BuilderContext clone(final FeatureProvider featureProvider) {
         final BuilderContext ctx = new BuilderContext(featureProvider);
         ctx.setArtifactProvider(this.artifactProvider);
+        ctx.artifactsOverrides.addAll(this.artifactsOverrides);
         ctx.variables.putAll(this.variables);
         ctx.frameworkProperties.putAll(this.frameworkProperties);
         ctx.mergeExtensions.addAll(mergeExtensions);
         ctx.postProcessExtensions.addAll(postProcessExtensions);
-        ctx.merge = this.merge;
         return ctx;
     }
 }
diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
index 4dd5b82..6399487 100644
--- a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
@@ -16,9 +16,22 @@
  */
 package org.apache.sling.feature.builder;
 
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureConstants;
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
@@ -35,21 +48,11 @@
 import javax.json.JsonValue.ValueType;
 import javax.json.JsonWriter;
 
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.FeatureConstants;
-import org.apache.sling.feature.builder.BuilderContext.ArtifactMergeAlgorithm;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-
 /**
  * Utility methods for the builders
  */
 class BuilderUtil {
+    static final String CATCHALL_OVERRIDE = "*:*:";
 
     static boolean contains(String key, Iterable<Map.Entry<String, String>> iterable) {
         if (iterable != null) {
@@ -73,7 +76,7 @@ static String get(String key, Iterable<Map.Entry<String, String>> iterable) {
         return null;
     }
 
-    private static void mergeWithContextOverwrite(String type, Map<String,String> target, Map<String,String> source, Iterable<Map.Entry<String,String>> context) {
+    private static void mergeWithContextOverride(String type, Map<String,String> target, Map<String,String> source, Iterable<Map.Entry<String,String>> context) {
         Map<String,String> result = new HashMap<>();
         for (Map.Entry<String, String> entry : target.entrySet()) {
             result.put(entry.getKey(), contains(entry.getKey(), context) ? get(entry.getKey(), context) : entry.getValue());
@@ -88,7 +91,7 @@ private static void mergeWithContextOverwrite(String type, Map<String,String> ta
                     String targetValue = target.get(entry.getKey());
                     if (targetValue != null) {
                         if (!value.equals(targetValue)) {
-                            throw new IllegalStateException(String.format("Can't merge %s '%s' defined twice (as '%s' v.s. '%s') and not overwritten.", type, entry.getKey(), value, targetValue));
+                            throw new IllegalStateException(String.format("Can't merge %s '%s' defined twice (as '%s' v.s. '%s') and not overridden.", type, entry.getKey(), value, targetValue));
                         }
                     }
                     else {
@@ -106,8 +109,8 @@ else if (!contains(entry.getKey(), target.entrySet())) {
 
     // variables
     static void mergeVariables(Map<String,String> target, Map<String,String> source, BuilderContext context) {
-        mergeWithContextOverwrite("Variable", target, source,
-                (null != context) ? context.getVariablesOverwrites().entrySet() : null);
+        mergeWithContextOverride("Variable", target, source,
+                (null != context) ? context.getVariablesOverrides().entrySet() : null);
     }
 
     /**
@@ -115,32 +118,40 @@ static void mergeVariables(Map<String,String> target, Map<String,String> source,
      *
      * @param target             The target bundles
      * @param source             The source bundles
-     * @param originatingFeature Optional, if set origin will be recorded
+     * @param sourceFeature Optional, if set origin will be recorded
      * @param artifactMergeAlg   Algorithm used to merge the artifacts
      */
     static void mergeBundles(final Bundles target,
         final Bundles source,
-        final Feature originatingFeature,
-            final ArtifactMergeAlgorithm artifactMergeAlg) {
+        final Feature sourceFeature,
+        final List<String> artifactOverrides) {
         for(final Map.Entry<Integer, List<Artifact>> entry : source.getBundlesByStartOrder().entrySet()) {
             for(final Artifact a : entry.getValue()) {
-                // version handling - use provided algorithm
-                boolean replace = true;
-                if (artifactMergeAlg == ArtifactMergeAlgorithm.HIGHEST) {
-                    final Artifact existing = target.getSame(a.getId());
-                    if ( existing != null && existing.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
-                        replace = false;
+                Artifact existing = target.getSame(a.getId());
+                List<Artifact> selectedArtifacts = null;
+                if (existing != null) {
+                    if (sourceFeature.getId().toMvnId().equals(
+                            existing.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))) {
+                        // If the source artifact came from the same feature, keep them side-by-side
+                        selectedArtifacts = Arrays.asList(existing, a);
+                    } else {
+                        selectedArtifacts = selectArtifactOverride(existing, a, artifactOverrides);
+                        while(target.removeSame(existing.getId())) {
+                            // Keep executing removeSame() which ignores the version until last one was removed
+                        }
                     }
+                } else {
+                    selectedArtifacts = Collections.singletonList(a);
                 }
-                if ( replace ) {
-                    target.removeSame(a.getId());
+
+                for (Artifact sa : selectedArtifacts) {
                     // create a copy to detach artifact from source
-                    final Artifact cp = a.copy(a.getId());
+                    final Artifact cp = sa.copy(sa.getId());
                     // Record the original feature of the bundle
-                    if (originatingFeature != null
-                            && a.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
+                    if (sourceFeature != null && source.contains(sa)
+                            && sa.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
                         cp.getMetadata().put(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE,
-                                originatingFeature.getId().toMvnId());
+                                sourceFeature.getId().toMvnId());
                     }
                     target.add(cp);
                 }
@@ -148,6 +159,51 @@ static void mergeBundles(final Bundles target,
         }
     }
 
+    static List<Artifact> selectArtifactOverride(Artifact a1, Artifact a2, List<String> artifactOverrides) {
+        if (a1.getId().equals(a2.getId())) {
+            // They're the same so return one of them
+            return Collections.singletonList(a2);
+        }
+
+        String a1gid = a1.getId().getGroupId();
+        String a1aid = a1.getId().getArtifactId();
+        String a2gid = a2.getId().getGroupId();
+        String a2aid = a2.getId().getArtifactId();
+
+        if (!a1gid.equals(a2gid))
+            throw new IllegalStateException("Artifacts must have the same group ID: " + a1 + " and " + a2);
+        if (!a1aid.equals(a2aid))
+            throw new IllegalStateException("Artifacts must have the same artifact ID: " + a1 + " and " + a2);
+
+        String prefix = a1gid + ":" + a1aid + ":";
+        for (String o : artifactOverrides) {
+            if (o.startsWith(prefix) || o.startsWith(CATCHALL_OVERRIDE)) {
+                int idx = o.lastIndexOf(':');
+                if (idx <= 0 || o.length() <= idx)
+                    continue;
+
+                String rule = o.substring(idx+1).trim();
+
+                if ("ALL".equals(rule)) {
+                    return Arrays.asList(a1, a2);
+                } else if ("HIGHEST".equals(rule)) {
+                    Version a1v = a1.getId().getOSGiVersion();
+                    Version a2v = a2.getId().getOSGiVersion();
+                    return a1v.compareTo(a2v) > 0 ? Collections.singletonList(a1) : Collections.singletonList(a2);
+                } else if ("LATEST".equals(rule)) {
+                    return Collections.singletonList(a2);
+                } else if (a1.getId().getVersion().equals(rule)) {
+                    return Collections.singletonList(a1);
+                } else if (a2.getId().getVersion().equals(rule)) {
+                    return Collections.singletonList(a2);
+                }
+                throw new IllegalStateException("Override rule " + o + " not applicable to artifacts " + a1 + " and " + a2);
+            }
+        }
+        throw new IllegalStateException("Artifact override rule required to select between these two artifacts " +
+                a1 + " and " + a2);
+    }
+
     // configurations - merge / override
     static void mergeConfigurations(final Configurations target, final Configurations source, final Feature origin) {
         for(final Configuration cfg : source) {
@@ -181,8 +237,8 @@ static void mergeConfigurations(final Configurations target, final Configuration
 
     // framework properties (add/merge)
     static void mergeFrameworkProperties(final Map<String,String> target, final Map<String,String> source, BuilderContext context) {
-        mergeWithContextOverwrite("Property", target, source,
-                context != null ? context.getFrameworkPropertiesOverwrites().entrySet() : null);
+        mergeWithContextOverride("Property", target, source,
+                context != null ? context.getFrameworkPropertiesOverrides().entrySet() : null);
     }
 
     // requirements (add)
@@ -208,13 +264,13 @@ static void mergeCapabilities(final List<Capability> target, final List<Capabili
      *
      * @param target             The target extension
      * @param source             The source extension
-     * @param originatingFeature Optional, if set origin will be recorded for
-     *                           artifacts
+     * @param originatingFeature Optional, if set origin will be recorded for artifacts
      * @param artifactMergeAlg   The merge algorithm for artifacts
      */
     static void mergeExtensions(final Extension target,
-            final Extension source, final Feature originatingFeature,
-            final ArtifactMergeAlgorithm artifactMergeAlg) {
+            final Extension source,
+            final Feature sourceFeature,
+            final List<String> artifactOverrides) {
         switch ( target.getType() ) {
             case TEXT : // simply append
                 target.setText(target.getText() + "\n" + source.getText());
@@ -254,25 +310,32 @@ static void mergeExtensions(final Extension target,
                 break;
 
         case ARTIFACTS:
-            for (final Artifact a : source.getArtifacts()) {
-                // use artifactMergeAlg
-                boolean replace = true;
-                if (artifactMergeAlg == ArtifactMergeAlgorithm.HIGHEST) {
-                    final Artifact existing = target.getArtifacts().getSame(a.getId());
-                    if (existing != null
-                            && existing.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0) {
-                        replace = false;
+            for(final Artifact a : source.getArtifacts()) {
+                Artifact existing = target.getArtifacts().getSame(a.getId());
+                List<Artifact> selectedArtifacts = null;
+                if (existing != null) {
+                    if (sourceFeature.getId().toMvnId().equals(
+                            existing.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))) {
+                        // If the source artifact came from the same feature, keep them side-by-side
+                        selectedArtifacts = Arrays.asList(existing, a);
+                    } else {
+                        selectedArtifacts = selectArtifactOverride(existing, a, artifactOverrides);
+                        while(target.getArtifacts().removeSame(existing.getId())) {
+                            // Keep executing removeSame() which ignores the version until last one was removed
+                        }
                     }
+                } else {
+                    selectedArtifacts = Collections.singletonList(a);
                 }
-                if (replace) {
-                    target.getArtifacts().removeSame(a.getId());
+
+                for (Artifact sa : selectedArtifacts) {
                     // create a copy to detach artifact from source
-                    final Artifact cp = a.copy(a.getId());
-                    // Record the original feature of the artifact
-                    if (originatingFeature != null
-                            && a.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
+                    final Artifact cp = sa.copy(sa.getId());
+                    // Record the original feature of the bundle
+                    if (sourceFeature != null && source.getArtifacts().contains(sa)
+                            && sa.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
                         cp.getMetadata().put(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE,
-                                originatingFeature.getId().toMvnId());
+                                sourceFeature.getId().toMvnId());
                     }
                     target.getArtifacts().add(cp);
                 }
@@ -284,7 +347,8 @@ static void mergeExtensions(final Extension target,
     // extensions (add/merge)
     static void mergeExtensions(final Feature target,
         final Feature source,
-            final ArtifactMergeAlgorithm artifactMergeAlg, final BuilderContext context, final boolean recordOrigin) {
+        final BuilderContext context,
+        final List<String> artifactOverrides) {
         for(final Extension ext : source.getExtensions()) {
             boolean found = false;
 
@@ -306,7 +370,7 @@ static void mergeExtensions(final Feature target,
                     }
                     if ( !handled ) {
                         // default merge
-                        mergeExtensions(current, ext, recordOrigin ? source : null, artifactMergeAlg);
+                        mergeExtensions(current, ext, source, artifactOverrides);
                     }
                 }
             }
diff --git a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
index f844443..f079b19 100644
--- a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
+++ b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
@@ -16,16 +16,6 @@
  */
 package org.apache.sling.feature.builder;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Configuration;
@@ -36,6 +26,17 @@
 import org.apache.sling.feature.Include;
 import org.osgi.framework.Version;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public abstract class FeatureBuilder {
 
     /** Pattern for using variables. */
@@ -195,7 +196,7 @@ public static Feature assemble(
             }
             usedFeatures.add(assembled.getId());
 
-            merge(target, assembled, context, context.getMergeAlgorithm(), true);
+            merge(target, assembled, context, context.getArtifactOverrides());
         }
 
         // append feature list in extension
@@ -323,10 +324,14 @@ private static Feature internalAssemble(final List<String> processedFeatures,
             // process include instructions
             processInclude(includedFeature, i);
 
-            // and now merge
-            merge(result, includedFeature, context, BuilderContext.ArtifactMergeAlgorithm.LATEST, true);
+            // and now merge the included feature into the result. No overrides should be needed since the result is empty before
+            merge(result, includedFeature, context, Collections.emptyList());
+
+            // and merge the current feature over the included feature into the result
+            merge(result, feature, context, Collections.singletonList("*:*:LATEST"));
 
-            merge(result, feature, context, BuilderContext.ArtifactMergeAlgorithm.LATEST, false);
+            // set the origin of the artifacts and configurations included and part of the resulting feature back to null
+            resetOrigins(result, includedFeature);
         }
 
         result.setAssembled(true);
@@ -339,18 +344,14 @@ private static Feature internalAssemble(final List<String> processedFeatures,
     private static void merge(final Feature target,
             final Feature source,
             final BuilderContext context,
-            final BuilderContext.ArtifactMergeAlgorithm mergeAlg, final boolean recordOrigin) {
+            final List<String> artifactOverrides) {
         BuilderUtil.mergeVariables(target.getVariables(), source.getVariables(), context);
-        BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), recordOrigin ? source : null, mergeAlg);
-        BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations(),
-                recordOrigin ? source : null);
+        BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), source, artifactOverrides);
+        BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations(), source);
         BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), source.getFrameworkProperties(), context);
         BuilderUtil.mergeRequirements(target.getRequirements(), source.getRequirements());
         BuilderUtil.mergeCapabilities(target.getCapabilities(), source.getCapabilities());
-        BuilderUtil.mergeExtensions(target,
-                source,
-                mergeAlg,
-                context, recordOrigin);
+        BuilderUtil.mergeExtensions(target, source, context, artifactOverrides);
     }
 
     /**
@@ -466,4 +467,34 @@ private static void processInclude(final Feature feature, final Include include)
             }
         }
     }
+
+    // Set the origin of elements coming from an included feature to the target feature
+    private static void resetOrigins(Feature feature, Feature includedFeature) {
+        String currentID = feature.getId().toMvnId();
+        String includedID = includedFeature.getId().toMvnId();
+
+        // As the feature is a prototype, set the origins to the target where it's going to
+        for (Artifact a : feature.getBundles()) {
+            Map<String, String> md = a.getMetadata();
+            if (currentID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))
+                    || includedID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE)))
+                md.remove(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE);
+        }
+        for (Configuration c : feature.getConfigurations()) {
+            Dictionary<String, Object> props = c.getProperties();
+            if (currentID.equals(props.get(Configuration.PROP_ORIGINAL__FEATURE))
+                    || includedID.equals(props.get(Configuration.PROP_ORIGINAL__FEATURE)))
+                props.remove(Configuration.PROP_ORIGINAL__FEATURE);
+        }
+        for (Extension e : feature.getExtensions()) {
+            if (ExtensionType.ARTIFACTS == e.getType()) {
+                for (Artifact a : e.getArtifacts()) {
+                    Map<String, String> md = a.getMetadata();
+                    if (currentID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))
+                            || includedID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE)))
+                        md.remove(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE);
+                }
+            }
+        }
+    }
 }
diff --git a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
index 5fd187d..23f3338 100644
--- a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
@@ -22,7 +22,6 @@
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.builder.BuilderContext.ArtifactMergeAlgorithm;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -30,6 +29,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -105,7 +105,8 @@ private int assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", "123", null, null));
 
-        BuilderUtil.mergeBundles(target, source, orgFeat, ArtifactMergeAlgorithm.HIGHEST);
+        List<String> overrides = Arrays.asList("g:a:HIGHEST", "g:b:HIGHEST");
+        BuilderUtil.mergeBundles(target, source, orgFeat, overrides);
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(3, result.size());
@@ -133,7 +134,8 @@ private int assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", "123", null, null));
 
-        BuilderUtil.mergeBundles(target, source, orgFeat, ArtifactMergeAlgorithm.LATEST);
+        List<String> overrides = Arrays.asList("g:a:LATEST", "g:b:LATEST");
+        BuilderUtil.mergeBundles(target, source, orgFeat, overrides);
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(3, result.size());
@@ -151,7 +153,8 @@ private int assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
         source.add(createBundle("g/a/1.1", 2));
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", "123", null, null));
-        BuilderUtil.mergeBundles(target, source, orgFeat, ArtifactMergeAlgorithm.LATEST);
+        List<String> overrides = Arrays.asList("g:a:LATEST", "g:b:LATEST");
+        BuilderUtil.mergeBundles(target, source, orgFeat, overrides);
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(1, result.size());
@@ -171,7 +174,7 @@ private int assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
         source.add(createBundle("g/f/2.5", 3));
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", "123", null, null));
-        BuilderUtil.mergeBundles(target, source, orgFeat, ArtifactMergeAlgorithm.LATEST);
+        BuilderUtil.mergeBundles(target, source, orgFeat, new ArrayList<>());
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(6, result.size());
@@ -191,11 +194,11 @@ private int assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
         source.add(createBundle("g/b/1.0", 1));
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", "123", "c1", "slingfeature"));
-        BuilderUtil.mergeBundles(target, source, orgFeat, ArtifactMergeAlgorithm.LATEST);
+        BuilderUtil.mergeBundles(target, source, orgFeat, new ArrayList<>());
 
         final Bundles target2 = new Bundles();
         final Feature orgFeat2 = new Feature(new ArtifactId("g", "a", "1", null, null));
-        BuilderUtil.mergeBundles(target2, target, orgFeat2, ArtifactMergeAlgorithm.LATEST);
+        BuilderUtil.mergeBundles(target2, target, orgFeat2, new ArrayList<>());
 
         List<Entry<Integer, Artifact>> result = getBundles(target2);
         assertEquals(2, result.size());
@@ -214,7 +217,7 @@ private int assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
 
         source.setJSON("[\"source1\",\"source2\"]");
 
-        BuilderUtil.mergeExtensions(target, source, null, ArtifactMergeAlgorithm.HIGHEST);
+        BuilderUtil.mergeExtensions(target, source, null, new ArrayList<>());
 
         assertEquals(target.getJSON(), "[\"target1\",\"target2\",\"source1\",\"source2\"]");
 
@@ -306,7 +309,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator gen, String ... exclusi
         Feature ft = new Feature(ArtifactId.fromMvnId("g:t:1"));
 
         assertEquals("Precondition", 0, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -330,7 +333,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator gen, String ... exclusi
         ft.getExtensions().add(et);
 
         assertEquals("Precondition", 1, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -357,7 +360,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator gen, String ... exclusi
         Feature ft = new Feature(ArtifactId.fromMvnId("g:t:1"));
 
         assertEquals("Precondition", 0, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -382,7 +385,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator gen, String ... exclusi
         ft.getExtensions().add(et);
 
         assertEquals("Precondition", 1, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -393,6 +396,80 @@ static void copyJsonObject(JsonObject obj, JsonGenerator gen, String ... exclusi
         assertEquals(er.readObject(), ar.readObject());
     }
 
+    @Test public void testSelectArtifactOverrideAll() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Arrays.asList("gid:aid2:1", "gid:aid:ALL ");
+        assertEquals(Arrays.asList(a1, a2), BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+    }
+
+    @Test public void testSelectArtifactOverrideIdenticalNeedsNoRule() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        assertEquals(Collections.singletonList(a1), BuilderUtil.selectArtifactOverride(a1, a2, Collections.emptyList()));
+    }
+
+    @Test public void testSelectArtifactOverride1() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:1");
+        assertEquals(Collections.singletonList(a1), BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+    }
+
+    @Test public void testSelectArtifactOverride2() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:2");
+        assertEquals(Collections.singletonList(a2), BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverride3() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:3");
+        BuilderUtil.selectArtifactOverride(a1, a2, overrides);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverrideDifferentGroupID() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("aid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:2");
+        BuilderUtil.selectArtifactOverride(a1, a2, overrides);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverrideDifferentArtifactID() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:gid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:2");
+        BuilderUtil.selectArtifactOverride(a1, a2, overrides);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverrideDifferentNoRule() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        BuilderUtil.selectArtifactOverride(a1, a2, Collections.emptyList());
+    }
+
+    @Test public void testSelectArtifactOverrideHigest() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1.1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2.0.1"));
+        List<String> overrides = Collections.singletonList("gid:aid:HIGHEST");
+        assertEquals(Collections.singletonList(a2), BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+        assertEquals(Collections.singletonList(a2), BuilderUtil.selectArtifactOverride(a2, a1, overrides));
+    }
+
+    @Test public void testSelectArtifactOverrideLatest() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1.1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2.0.1"));
+        List<String> overrides = Collections.singletonList("gid:aid:LATEST");
+        assertEquals(Collections.singletonList(a2), BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+        assertEquals(Collections.singletonList(a1), BuilderUtil.selectArtifactOverride(a2, a1, overrides));
+    }
+
     @SafeVarargs
     public static Artifact createBundle(final String id, final int startOrder, Map.Entry<String, String> ... metadata) {
         final Artifact a = new Artifact(ArtifactId.parse(id));
diff --git a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
index 91c6235..b81a7f2 100644
--- a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
@@ -16,23 +16,6 @@
  */
 package org.apache.sling.feature.builder;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.felix.utils.resource.CapabilityImpl;
 import org.apache.felix.utils.resource.RequirementImpl;
 import org.apache.sling.feature.Artifact;
@@ -47,6 +30,22 @@
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 public class FeatureBuilderTest {
 
     private static final Map<String, Feature> FEATURES = new HashMap<>();
@@ -71,6 +70,25 @@
         FEATURES.put(f1.getId().toMvnId(), f1);
     }
 
+    static {
+        final Feature f2 = new Feature(ArtifactId.parse("g/a/2"));
+
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/1", 4));
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/someart/1.2.3", 4));
+
+        FEATURES.put(f2.getId().toMvnId(), f2);
+    }
+
+    static {
+        final Feature f2 = new Feature(ArtifactId.parse("g/a/3"));
+
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/someart/1.2.3", 4));
+
+        FEATURES.put(f2.getId().toMvnId(), f2);
+    }
+
     private final FeatureProvider provider = new FeatureProvider() {
 
         @Override
@@ -276,16 +294,14 @@ private void equals(final Feature expected, final Feature actuals) {
         final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
         a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
         a1.getMetadata().put("hash", "4632463464363646436");
-        a1.getMetadata().put("org-feature", "org.apache.sling:test-feature:1.1");
         base.getBundles().add(a1);
-        Map.Entry<String, String> md = new AbstractMap.SimpleEntry<String, String>("org-feature", "org.apache.sling:test-feature:1.1");
-        base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1, md));
-        base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1, md));
-        base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2, md));
-        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1", 5, md));
-        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5", 5, md));
-        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 10, md));
-        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2", 10, md));
+        base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
+        base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
+        base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
+        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1", 5));
+        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5", 5));
+        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 10));
+        base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2", 10));
 
         final Configuration co1 = new Configuration("my.pid");
         co1.getProperties().put("foo", 5L);
@@ -304,11 +320,9 @@ private void equals(final Feature expected, final Feature actuals) {
         result.getVariables().put("varx", "myvalx");
         result.setInclude(null);
         result.getFrameworkProperties().put("bar", "X");
-        Map.Entry<String, String> md2 = new AbstractMap.SimpleEntry<String, String>("org-feature", "g:a:1");
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3, md2));
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
         final Configuration co3 = new Configuration("org.apache.sling.foo");
         co3.getProperties().put("prop", "value");
-        co3.getProperties().put(Configuration.PROP_ORIGINAL__FEATURE, i1.getId().toMvnId());
         result.getConfigurations().add(co3);
 
         // assemble
@@ -318,6 +332,103 @@ private void equals(final Feature expected, final Feature actuals) {
         equals(result, assembled);
     }
 
+    @Test public void testSingleIncludeMultiVersion() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:3"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("group:testmulti:1")));
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("group:testmulti:3")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(b1);
+        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        result.getBundles().add(b2);
+        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
+    @Test public void testSingleIncludeMultiVersion2() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:2"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        b1.setStartOrder(4);
+        result.getBundles().add(b1);
+        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:2"));
+        b2.setStartOrder(8);
+        result.getBundles().add(b2);
+        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
+    @Test public void testSingleIncludeMultiVersion3() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:2"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("group:testmulti:1")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(b1);
+        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
+    @Test public void testSingleIncludeMultiVersion4() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:2"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("group:testmulti:1")));
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("group:testmulti:3")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(b1);
+        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        result.getBundles().add(b2);
+        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
     @Test public void testDeduplicationInclude() throws Exception {
         final ArtifactId idA = ArtifactId.fromMvnId("g:a:1.0.0");
         final ArtifactId idB = ArtifactId.fromMvnId("g:b:1.0.0");
@@ -624,7 +735,7 @@ public Feature provide(ArtifactId id)
             {
                 return null;
             }
-                }).addVariablesOverwrites(override), aFeature, bFeature);
+                }).addVariablesOverrides(override), aFeature, bFeature);
 
         Map<String,String> vars = new HashMap<>();
         vars.putAll(kvMapA);
@@ -645,7 +756,7 @@ public Feature provide(ArtifactId id)
                 {
                     return null;
                 }
-                    }).addVariablesOverwrites(override), aFeature, bFeature);
+                    }).addVariablesOverrides(override), aFeature, bFeature);
             fail("Excepted merge exception");
         } catch (IllegalStateException expected) {}
 
@@ -658,7 +769,7 @@ public Feature provide(ArtifactId id)
             {
                 return null;
             }
-                }).addVariablesOverwrites(override), aFeature, bFeature);
+                }).addVariablesOverrides(override), aFeature, bFeature);
 
         vars = new HashMap<>();
         vars.putAll(kvMapA);
@@ -677,7 +788,7 @@ public Feature provide(ArtifactId id)
             {
                 return null;
             }
-                }).addVariablesOverwrites(override), aFeature, bFeature);
+                }).addVariablesOverrides(override), aFeature, bFeature);
 
         vars.put("var2", null);
         assertTrue(cFeature.getVariables().equals(vars));


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services