FELIX-308: initial implementation to support embedding dependencies

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@568873 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java
index 4f36806..486d912 100644
--- a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java
+++ b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java
@@ -221,6 +221,13 @@
             builder.setProperties(properties);
             builder.setClasspath(classpath);
 
+            Collection embeddableArtifacts = getEmbeddableArtifacts(properties);
+            if (embeddableArtifacts.size() > 0)
+            {
+                // add BND instructions to embed selected dependencies
+                new DependencyEmbedder(embeddableArtifacts).processHeaders(properties);
+            }
+
             builder.build();
             Jar jar = builder.getJar();
             this.doMavenMetadata(project, jar);
@@ -538,4 +545,19 @@
 
         return resourcePaths.toString();
     }
+
+    Collection getEmbeddableArtifacts(Properties properties)
+    {
+        String embedTransitive = properties.getProperty(DependencyEmbedder.EMBED_TRANSITIVE);
+        if (Boolean.valueOf(embedTransitive).booleanValue())
+        {
+            // includes transitive dependencies
+            return project.getArtifacts();
+        }
+        else
+        {
+            // only includes direct dependencies
+            return project.getDependencyArtifacts();
+        }
+    }
 }
diff --git a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/DependencyEmbedder.java b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/DependencyEmbedder.java
new file mode 100644
index 0000000..7d107a8
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/DependencyEmbedder.java
@@ -0,0 +1,362 @@
+/*
+ * 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.felix.bundleplugin;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+
+import aQute.lib.osgi.Analyzer;
+import aQute.lib.osgi.Instruction;
+import aQute.lib.osgi.header.OSGiHeader;
+
+/**
+ * Add BND directives to embed selected dependencies inside a bundle
+ * 
+ * @author stuart.mcculloch@jayway.net (Stuart McCulloch)
+ */
+public class DependencyEmbedder
+{
+    public static String EMBED_DEPENDENCY    = "Embed-Dependency";
+    public static String EMBED_DIRECTORY     = "Embed-Directory";
+    public static String EMBED_STRIP_GROUP   = "Embed-StripGroup";
+    public static String EMBED_STRIP_VERSION = "Embed-StripVersion";
+    public static String EMBED_TRANSITIVE    = "Embed-Transitive";
+
+    /**
+     * Dependency artifacts.
+     */
+    private final Collection dependencyArtifacts;
+
+    /**
+     * Inlined artifacts.
+     */
+    private final Collection inlinedArtifacts;
+
+    /**
+     * Embedded artifacts.
+     */
+    private final Collection embeddedArtifacts;
+
+    public DependencyEmbedder(Collection dependencyArtifacts)
+    {
+        this.dependencyArtifacts = dependencyArtifacts;
+
+        this.inlinedArtifacts = new HashSet();
+        this.embeddedArtifacts = new HashSet();
+    }
+
+    public void processHeaders(Properties properties)
+    throws MojoExecutionException
+    {
+        this.inlinedArtifacts.clear();
+        this.embeddedArtifacts.clear();
+
+        String embedDependencyHeader = properties.getProperty(EMBED_DEPENDENCY);
+        if (null != embedDependencyHeader && embedDependencyHeader.length() > 0)
+        {
+            Map embedInstructions = OSGiHeader.parseHeader(embedDependencyHeader);
+            this.processEmbedInstructions(embedInstructions);
+
+            for (Iterator i = this.inlinedArtifacts.iterator(); i.hasNext();)
+            {
+                DependencyEmbedder.inlineDependency(properties, (Artifact)i.next());
+            }
+            for (Iterator i = this.embeddedArtifacts.iterator(); i.hasNext();)
+            {
+                DependencyEmbedder.embedDependency(properties, (Artifact)i.next());
+            }
+        }
+    }
+
+    protected static abstract class DependencyFilter
+    {
+        Instruction instruction;
+        String defaultValue;
+
+        public DependencyFilter(String expression)
+        {
+            this(expression, "");
+        }
+
+        public DependencyFilter(String expression, String defaultValue)
+        {
+            this.instruction = Instruction.getPattern(expression);
+            this.defaultValue = defaultValue;
+        }
+
+        public void filter(Collection dependencies)
+        {
+            for (Iterator i = dependencies.iterator(); i.hasNext();)
+            {
+                if (false == matches((Artifact)i.next()))
+                {
+                    i.remove();
+                }
+            }
+        }
+
+        abstract boolean matches(Artifact dependency);
+
+        boolean matches(String text)
+        {
+            if (null == text)
+            {
+                text = defaultValue;
+            }
+            boolean result = this.instruction.matches(text);
+            return this.instruction.isNegated() ? !result : result;
+        }
+    }
+
+    protected void processEmbedInstructions(Map embedInstructions)
+    throws MojoExecutionException
+    {
+        DependencyFilter filter;
+        for (Iterator clauseIterator = embedInstructions.entrySet().iterator(); clauseIterator.hasNext();)
+        {
+            boolean inline = false;
+
+            // must use a fresh *modifiable* collection for each unique clause
+            Collection filteredDependencies = new HashSet(this.dependencyArtifacts);
+
+            // CLAUSE: REGEXP --> { ATTRIBUTE MAP }
+            Map.Entry clause = (Map.Entry)clauseIterator.next();
+
+            filter = new DependencyFilter((String)clause.getKey())
+            {
+                boolean matches(Artifact dependency)
+                {
+                    return super.matches(dependency.getArtifactId());
+                }
+            };
+
+            // FILTER ON MAIN CLAUSE
+            filter.filter(filteredDependencies);
+
+            for (Iterator attrIterator = ((Map)clause.getValue()).entrySet().iterator(); attrIterator.hasNext();)
+            {
+                // ATTRIBUTE: KEY --> REGEXP
+                Map.Entry attr = (Map.Entry)attrIterator.next();
+
+                if ("groupId".equals(attr.getKey()))
+                {
+                    filter = new DependencyFilter((String)attr.getValue())
+                    {
+                        boolean matches(Artifact dependency)
+                        {
+                            return super.matches(dependency.getGroupId());
+                        }
+                    };
+                }
+                else if ("artifactId".equals(attr.getKey()))
+                {
+                    filter = new DependencyFilter((String)attr.getValue())
+                    {
+                        boolean matches(Artifact dependency)
+                        {
+                            return super.matches(dependency.getArtifactId());
+                        }
+                    };
+                }
+                else if ("version".equals(attr.getKey()))
+                {
+                    filter = new DependencyFilter((String)attr.getValue())
+                    {
+                        boolean matches(Artifact dependency)
+                        {
+                            try
+                            {
+                                // use the symbolic version if available (ie. 1.0.0-SNAPSHOT)
+                                return super.matches(dependency.getSelectedVersion().toString());
+                            }
+                            catch (Exception e)
+                            {
+                                return super.matches(dependency.getVersion());
+                            }
+                        }
+                    };
+                }
+                else if ("scope".equals(attr.getKey()))
+                {
+                    filter = new DependencyFilter((String)attr.getValue(), "compile")
+                    {
+                        boolean matches(Artifact dependency)
+                        {
+                            return super.matches(dependency.getScope());
+                        }
+                    };
+                }
+                else if ("type".equals(attr.getKey()))
+                {
+                    filter = new DependencyFilter((String)attr.getValue(), "jar")
+                    {
+                        boolean matches(Artifact dependency)
+                        {
+                            return super.matches(dependency.getType());
+                        }
+                    };
+                }
+                else if ("classifier".equals(attr.getKey()))
+                {
+                    filter = new DependencyFilter((String)attr.getValue())
+                    {
+                        boolean matches(Artifact dependency)
+                        {
+                            return super.matches(dependency.getClassifier());
+                        }
+                    };
+                }
+                else if ("optional".equals(attr.getKey()))
+                {
+                    filter = new DependencyFilter((String)attr.getValue(), "false")
+                    {
+                        boolean matches(Artifact dependency)
+                        {
+                            return super.matches(""+dependency.isOptional());
+                        }
+                    };
+                }
+                else if ("inline".equals(attr.getKey()))
+                {
+                    inline = Boolean.valueOf((String)attr.getValue()).booleanValue();
+
+                    continue;
+                }
+                else
+                {
+                    throw new MojoExecutionException("Unexpected attribute " + attr.getKey());
+                }
+
+                // FILTER ON EACH ATTRIBUTE
+                filter.filter(filteredDependencies);
+            }
+
+            if (inline)
+            {
+                this.inlinedArtifacts.addAll(filteredDependencies);
+            }
+            else
+            {
+                this.embeddedArtifacts.addAll(filteredDependencies);
+            }
+        }
+
+        // remove any inlined artifacts from the embedded list
+        this.embeddedArtifacts.removeAll(this.inlinedArtifacts);
+    }
+
+    public static void embedDependency(Properties properties, Artifact dependency)
+    {
+        File jarFile = dependency.getFile();
+
+        if (null != jarFile && jarFile.exists())
+        {
+            String bundleClassPath = properties.getProperty(Analyzer.BUNDLE_CLASSPATH);
+            String includeResource = properties.getProperty(Analyzer.INCLUDE_RESOURCE);
+
+            if (null == bundleClassPath)
+            {
+                bundleClassPath = ".,";
+            }
+            else if (bundleClassPath.length() > 0)
+            {
+                bundleClassPath += ",";
+            }
+
+            if (null == includeResource)
+            {
+                includeResource = "";
+            }
+            else if (includeResource.length() > 0)
+            {
+                includeResource += ",";
+            }
+
+            String embedDirectory    = properties.getProperty(EMBED_DIRECTORY);
+            String embedStripGroup   = properties.getProperty(EMBED_STRIP_GROUP);
+            String embedStripVersion = properties.getProperty(EMBED_STRIP_VERSION);
+
+            if ("".equals(embedDirectory) || ".".equals(embedDirectory))
+            {
+                embedDirectory = null;
+            }
+
+            if (false == Boolean.valueOf(embedStripGroup).booleanValue())
+            {
+                embedDirectory = new File(embedDirectory, dependency.getGroupId()).getPath();
+            }
+
+            File targetFile;
+            if (Boolean.valueOf(embedStripVersion).booleanValue())
+            {
+                targetFile = new File(embedDirectory, dependency.getArtifactId() + ".jar");
+            }
+            else
+            {
+                targetFile = new File(embedDirectory, jarFile.getName());
+            }
+
+            bundleClassPath += targetFile;
+            includeResource += targetFile + "=" + jarFile;
+
+            properties.setProperty(Analyzer.BUNDLE_CLASSPATH, bundleClassPath);
+            properties.setProperty(Analyzer.INCLUDE_RESOURCE, includeResource);
+        }
+    }
+
+    public static void inlineDependency(Properties properties, Artifact dependency)
+    {
+        File jarFile = dependency.getFile();
+
+        if (null != jarFile && jarFile.exists())
+        {
+            String includeResource = properties.getProperty(Analyzer.INCLUDE_RESOURCE);
+
+            if (null == includeResource)
+            {
+                includeResource = "";
+            }
+            else if (includeResource.length() > 0)
+            {
+                includeResource += ",";
+            }
+
+            includeResource += "@" + jarFile;
+
+            properties.setProperty(Analyzer.INCLUDE_RESOURCE, includeResource);
+        }
+    }
+
+    public Collection getInlinedArtifacts()
+    {
+        return this.inlinedArtifacts;
+    }
+
+    public Collection getEmbeddedArtifacts()
+    {
+        return this.embeddedArtifacts;
+    }
+}