FELIX-3185: negative Embed-Dependency clauses (such as !*;groupId=example) should restrict available dependencies

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1189530 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/AbstractDependencyFilter.java b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/AbstractDependencyFilter.java
index 2974ffa..9b5d0cf 100644
--- a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/AbstractDependencyFilter.java
+++ b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/AbstractDependencyFilter.java
@@ -20,8 +20,8 @@
 
 
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.regex.Pattern;
 
@@ -39,7 +39,7 @@
  */
 public abstract class AbstractDependencyFilter
 {
-    private static final Pattern MISSING_KEY_PATTERN = Pattern.compile( "^(!)?([a-zA-Z]+=)" );
+    private static final Pattern MISSING_KEY_PATTERN = Pattern.compile( "(^|,)\\p{Blank}*(!)?\\p{Blank}*([a-zA-Z]+=)" );
 
     /**
      * Dependency artifacts.
@@ -106,20 +106,27 @@
 
     protected final void processInstructions( String header ) throws MojoExecutionException
     {
-        Map instructions = OSGiHeader.parseHeader( MISSING_KEY_PATTERN.matcher( header ).replaceFirst( "$1*;$2" ) );
+        Map instructions = OSGiHeader.parseHeader( MISSING_KEY_PATTERN.matcher( header ).replaceAll( "$1$2*;$3" ) );
+
+        Collection availableDependencies = new LinkedHashSet( m_dependencyArtifacts );
 
         DependencyFilter filter;
         for ( Iterator clauseIterator = instructions.entrySet().iterator(); clauseIterator.hasNext(); )
         {
             String inline = "false";
 
-            // must use a fresh *modifiable* collection for each unique clause
-            Collection filteredDependencies = new HashSet( m_dependencyArtifacts );
+            // always start with a fresh *modifiable* collection for each unique clause
+            Collection filteredDependencies = new LinkedHashSet( availableDependencies );
 
             // CLAUSE: REGEXP --> { ATTRIBUTE MAP }
             Map.Entry clause = ( Map.Entry ) clauseIterator.next();
-
             String primaryKey = ( ( String ) clause.getKey() ).replaceFirst( "~+$", "" );
+            boolean isNegative = primaryKey.startsWith( "!" );
+            if ( isNegative )
+            {
+                primaryKey = primaryKey.substring( 1 );
+            }
+
             if ( !"*".equals( primaryKey ) )
             {
                 filter = new DependencyFilter( primaryKey )
@@ -229,7 +236,21 @@
                 filter.filter( filteredDependencies );
             }
 
-            processDependencies( filteredDependencies, inline );
+            if ( isNegative )
+            {
+                // negative clauses reduce the set of available artifacts
+                availableDependencies.removeAll( filteredDependencies );
+                if ( !clauseIterator.hasNext() )
+                {
+                    // assume there's an implicit * missing at the end
+                    processDependencies( availableDependencies, inline );
+                }
+            }
+            else
+            {
+                // positive clause; doesn't alter the available artifacts
+                processDependencies( filteredDependencies, inline );
+            }
         }
     }
 
diff --git a/bundleplugin/src/test/java/org/apache/felix/bundleplugin/BundlePluginTest.java b/bundleplugin/src/test/java/org/apache/felix/bundleplugin/BundlePluginTest.java
index 9ecf37c..e224c96 100644
--- a/bundleplugin/src/test/java/org/apache/felix/bundleplugin/BundlePluginTest.java
+++ b/bundleplugin/src/test/java/org/apache/felix/bundleplugin/BundlePluginTest.java
@@ -245,6 +245,35 @@
     }
 
 
+    public void testEmbedDependencyNegativeClauses() throws Exception
+    {
+        ArtifactStubFactory artifactFactory = new ArtifactStubFactory( plugin.getOutputDirectory(), true );
+
+        Set artifacts = new LinkedHashSet();
+
+        artifacts.addAll( artifactFactory.getClassifiedArtifacts() );
+        artifacts.addAll( artifactFactory.getScopedArtifacts() );
+        artifacts.addAll( artifactFactory.getTypedArtifacts() );
+
+        MavenProject project = getMavenProjectStub();
+        project.setDependencyArtifacts( artifacts );
+
+        Map instructions = new HashMap();
+        instructions.put( DependencyEmbedder.EMBED_DEPENDENCY, "!type=jar, !artifactId=c" );
+        Properties props = new Properties();
+
+        Builder builder = plugin.buildOSGiBundle( project, instructions, props, plugin.getClasspath( project ) );
+        Manifest manifest = builder.getJar().getManifest();
+
+        String bcp = manifest.getMainAttributes().getValue( Constants.BUNDLE_CLASSPATH );
+        assertEquals( ".," + "e-1.0.rar," + "a-1.0.war," + "d-1.0.zip", bcp );
+
+        String eas = manifest.getMainAttributes().getValue( "Embedded-Artifacts" );
+        assertEquals( "e-1.0.rar;g=\"g\";a=\"e\";v=\"1.0\"," + "a-1.0.war;g=\"g\";a=\"a\";v=\"1.0\","
+            + "d-1.0.zip;g=\"g\";a=\"d\";v=\"1.0\"", eas );
+    }
+
+
     public void testEmbedDependencyDuplicateKeys() throws Exception
     {
         ArtifactStubFactory artifactFactory = new ArtifactStubFactory( plugin.getOutputDirectory(), true );
@@ -274,11 +303,6 @@
     }
 
 
-    public void testEmbedDependencyNegativeClauses() throws Exception
-    {
-    }
-
-
     public void testEmbedDependencyMissingPositiveKey() throws Exception
     {
         ArtifactStubFactory artifactFactory = new ArtifactStubFactory( plugin.getOutputDirectory(), true );
@@ -300,15 +324,51 @@
         Manifest manifest = builder.getJar().getManifest();
 
         String bcp = manifest.getMainAttributes().getValue( Constants.BUNDLE_CLASSPATH );
-        assertEquals( ".," + "a-1.0.war," + "a-1.0-one.jar," + "b-1.0.jar," + "b-1.0-two.jar", bcp );
+        assertEquals( ".," + "a-1.0-one.jar," + "b-1.0-two.jar," + "a-1.0.war," + "b-1.0.jar", bcp );
 
         String eas = manifest.getMainAttributes().getValue( "Embedded-Artifacts" );
-        assertEquals( "a-1.0.war;g=\"g\";a=\"a\";v=\"1.0\"," + "a-1.0-one.jar;g=\"g\";a=\"a\";v=\"1.0\";c=\"one\","
-            + "b-1.0.jar;g=\"g\";a=\"b\";v=\"1.0\"," + "b-1.0-two.jar;g=\"g\";a=\"b\";v=\"1.0\";c=\"two\"", eas );
+        assertEquals( "a-1.0-one.jar;g=\"g\";a=\"a\";v=\"1.0\";c=\"one\","
+            + "b-1.0-two.jar;g=\"g\";a=\"b\";v=\"1.0\";c=\"two\"," + "a-1.0.war;g=\"g\";a=\"a\";v=\"1.0\","
+            + "b-1.0.jar;g=\"g\";a=\"b\";v=\"1.0\"", eas );
     }
 
 
     public void testEmbedDependencyMissingNegativeKey() throws Exception
     {
+        ArtifactStubFactory artifactFactory = new ArtifactStubFactory( plugin.getOutputDirectory(), true );
+
+        Set artifacts = new LinkedHashSet();
+
+        artifacts.addAll( artifactFactory.getClassifiedArtifacts() );
+        artifacts.addAll( artifactFactory.getScopedArtifacts() );
+        artifacts.addAll( artifactFactory.getTypedArtifacts() );
+
+        MavenProject project = getMavenProjectStub();
+        project.setDependencyArtifacts( artifacts );
+        Properties props = new Properties();
+
+        Map instructions1 = new HashMap();
+        instructions1.put( DependencyEmbedder.EMBED_DEPENDENCY, "!scope=compile" );
+        Builder builder1 = plugin.buildOSGiBundle( project, instructions1, props, plugin.getClasspath( project ) );
+        Manifest manifest1 = builder1.getJar().getManifest();
+
+        Map instructions2 = new HashMap();
+        instructions2.put( DependencyEmbedder.EMBED_DEPENDENCY, "scope=!compile" );
+        Builder builder2 = plugin.buildOSGiBundle( project, instructions2, props, plugin.getClasspath( project ) );
+        Manifest manifest2 = builder2.getJar().getManifest();
+
+        String bcp1 = manifest1.getMainAttributes().getValue( Constants.BUNDLE_CLASSPATH );
+        assertEquals( ".," + "test-1.0.jar," + "provided-1.0.jar," + "runtime-1.0.jar," + "system-1.0.jar", bcp1 );
+
+        String eas1 = manifest1.getMainAttributes().getValue( "Embedded-Artifacts" );
+        assertEquals( "test-1.0.jar;g=\"g\";a=\"test\";v=\"1.0\","
+            + "provided-1.0.jar;g=\"g\";a=\"provided\";v=\"1.0\"," + "runtime-1.0.jar;g=\"g\";a=\"runtime\";v=\"1.0\","
+            + "system-1.0.jar;g=\"g\";a=\"system\";v=\"1.0\"", eas1 );
+
+        String bcp2 = manifest2.getMainAttributes().getValue( Constants.BUNDLE_CLASSPATH );
+        assertEquals( bcp1, bcp2 );
+
+        String eas2 = manifest2.getMainAttributes().getValue( "Embedded-Artifacts" );
+        assertEquals( eas1, eas2 );
     }
 }