You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tuscany.apache.org by rf...@apache.org on 2010/01/05 19:00:36 UTC

svn commit: r896154 - in /tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin: AggregatedBundleActivator.java BundleAggregatorMojo.java HeaderParser.java

Author: rfeng
Date: Tue Jan  5 18:00:36 2010
New Revision: 896154

URL: http://svn.apache.org/viewvc?rev=896154&view=rev
Log:
Add new goal so that we can generate a single OSGi bundle from the modules

Added:
    tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java   (with props)
    tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java   (with props)
    tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java   (with props)

Added: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java
URL: http://svn.apache.org/viewvc/tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java?rev=896154&view=auto
==============================================================================
--- tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java (added)
+++ tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java Tue Jan  5 18:00:36 2010
@@ -0,0 +1,56 @@
+/*
+ * 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.tuscany.maven.bundle.plugin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * A bundle activator that delegates to others
+ */
+public class AggregatedBundleActivator implements BundleActivator {
+    public static final String BUNDLE_ACTIVATOR_LIST = "Tuscany-Bundle-Activator-List";
+    private List<BundleActivator> activators = new ArrayList<BundleActivator>();
+
+    public void start(BundleContext context) throws Exception {
+        String list = (String)context.getBundle().getHeaders().get(BUNDLE_ACTIVATOR_LIST);
+        if (list == null) {
+            return;
+        }
+        for (String cls : list.split(",")) {
+            Object i = context.getBundle().loadClass(cls).newInstance();
+            if (i instanceof BundleActivator) {
+                ((BundleActivator)i).start(context);
+                activators.add((BundleActivator)i);
+            }
+        }
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        for (BundleActivator a : activators) {
+            a.stop(context);
+        }
+
+    }
+
+}

Propchange: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java
URL: http://svn.apache.org/viewvc/tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java?rev=896154&view=auto
==============================================================================
--- tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java (added)
+++ tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java Tue Jan  5 18:00:36 2010
@@ -0,0 +1,263 @@
+/*
+ * 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.tuscany.maven.bundle.plugin;
+
+import static org.apache.tuscany.maven.bundle.plugin.AggregatedBundleActivator.BUNDLE_ACTIVATOR_LIST;
+import static org.apache.tuscany.maven.bundle.plugin.HeaderParser.merge;
+import static org.osgi.framework.Constants.BUNDLE_ACTIVATOR;
+import static org.osgi.framework.Constants.BUNDLE_CLASSPATH;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.tuscany.maven.bundle.plugin.HeaderParser.HeaderClause;
+
+/**
+ * @version $Rev$ $Date$
+ * @goal aggregate-modules
+ * @phase process-resources
+ * @requiresDependencyResolution test
+ * @description Generate an aggregated bundle that contains all the modules and 3rd party jars
+ */
+public class BundleAggregatorMojo extends AbstractMojo {
+    /**
+     * The project to create a distribution for.
+     *
+     * @parameter expression="${project}"
+     * @required
+     * @readonly
+     */
+    private MavenProject project;
+
+    /**
+     * Root directory.
+     *
+     *  @parameter expression="${project.build.directory}/modules"
+     */
+    private File rootDirectory;
+
+    /**
+     * Aggregated bundle
+     *
+     *  @parameter expression="${project.build.directory}/singlebundle/tuscany-bundle.jar"
+     */
+    private File targetBundleFile;
+
+    /**
+     * @parameter default-value== "org.apache.tuscany.sca.bundle";
+     */
+    private String bundleName = "org.apache.tuscany.sca.bundle";
+    
+    /**
+     * @parameter default-value== "2.0.0";
+     */
+    private String bundleVersion = "2.0.0";
+    
+    // private static final Logger logger = Logger.getLogger(BundleAggregatorMojo.class.getName());
+
+    public void aggregateBundles(File root, File targetBundleFile) throws Exception {
+        Log log = getLog();
+        if (!root.isDirectory()) {
+            log.warn(root + " is not a directory");
+            return;
+        }
+        targetBundleFile.getParentFile().mkdirs();
+        Set<File> jarFiles = new HashSet<File>();
+        List<Manifest> manifests = new ArrayList<Manifest>();
+        for (File child : root.listFiles()) {
+            try {
+                Manifest manifest = null;
+                if (child.isDirectory()) {
+                    File mf = new File(child, "META-INF/MANIFEST.MF");
+                    if (mf.isFile()) {
+                        FileInputStream is = new FileInputStream(mf);
+                        manifest = new Manifest(is);
+                        is.close();
+                        if (manifest != null) {
+                            String classpath = manifest.getMainAttributes().getValue("Bundle-ClassPath");
+                            if (classpath != null) {
+                                for (HeaderClause clause : HeaderParser.parse(classpath)) {
+                                    if (clause.getValue().equals(".")) {
+                                        continue;
+                                    } else {
+                                        jarFiles.add(new File(child, clause.getValue()));
+                                    }
+                                }
+                            } else {
+                                // 
+                            }
+                        }
+                    }
+                } else if (child.getName().endsWith(".jar")) {
+                    JarFile jar = new JarFile(child);
+                    manifest = jar.getManifest();
+                    jar.close();
+                    if (manifest != null) {
+                        String id = manifest.getMainAttributes().getValue("Bundle-SymbolicName");
+                        if (id != null && (id.startsWith("org.eclipse.") || id
+                            .startsWith("org.apache.tuscany.sca.gateway"))) {
+                            manifest = null;
+                        } else {
+                            jarFiles.add(child);
+                        }
+                    }
+                }
+                if (manifest == null) {
+                    continue;
+                }
+
+                log.debug("Bundle file: " + child);
+                manifests.add(manifest);
+            } catch (Exception e) {
+                throw e;
+            }
+        }
+        Manifest merged = new Manifest();
+        Attributes attributes = merged.getMainAttributes();
+        attributes.putValue("Manifest-Version", "1.0");
+        attributes.putValue("Bundle-ManifestVersion", "2");
+        attributes.putValue("Bundle-License", "http://www.apache.org/licenses/LICENSE-2.0.txt");
+        attributes.putValue("Bundle-DocURL", "http://www.apache.org/");
+        attributes.putValue("Bundle-RequiredExecutionEnvironment", "J2SE-1.5,JavaSE-1.6");
+        attributes.putValue("Bundle-Vendor", "The Apache Software Foundation");
+        attributes.putValue("Bundle-Version", bundleVersion);
+        attributes.putValue("Bundle-SymbolicName", bundleName);
+        attributes.putValue("SCA-Version", "1.1");
+        attributes.putValue("Bundle-Name", bundleName);
+        attributes.putValue("Bundle-ActivationPolicy", "lazy");
+        for (Manifest mf : manifests) {
+            for (Map.Entry<Object, Object> e : mf.getMainAttributes().entrySet()) {
+                Attributes.Name key = (Attributes.Name)e.getKey();
+                String name = key.toString();
+                String oldValue = attributes.getValue(name);
+                String value = (String)e.getValue();
+                if (name.equals("Export-Package") || name.equals("Import-Package")
+                    || name.equals("Require-Bundle")
+                    || name.equals("DynamicImport-Package")
+                    || name.equals("Bundle-ClassPath")
+                    || name.equals("Private-Package")
+                    || name.equals("Bundle-Description")) {
+                    attributes.putValue(name, merge(oldValue, value));
+                } else if (name.equals(BUNDLE_ACTIVATOR)) {
+                    oldValue = attributes.getValue(BUNDLE_ACTIVATOR_LIST);
+                    attributes.putValue(BUNDLE_ACTIVATOR_LIST, merge(oldValue, value));
+                } else if (name.equals("Main-Class") || name.startsWith("Eclipse-") || name.startsWith("Bundle-")) {
+                    // Ignore
+                } else {
+                    // Ignore
+                    // attributes.putValue(name, value);
+                }
+            }
+        }
+        log.info("Generating " + targetBundleFile);
+        attributes.putValue(BUNDLE_ACTIVATOR, AggregatedBundleActivator.class.getName());
+        String bundleClassPath = attributes.getValue(BUNDLE_CLASSPATH);
+        bundleClassPath = merge(bundleClassPath, ".");
+        for (File f : jarFiles) {
+            bundleClassPath = merge(bundleClassPath, f.getName());
+        }
+        attributes.putValue(BUNDLE_CLASSPATH, bundleClassPath);
+
+        FileOutputStream fos = new FileOutputStream(targetBundleFile);
+        JarOutputStream bundle = new JarOutputStream(fos, merged);
+
+        for (File file : jarFiles) {
+            log.info("Adding " + file);
+            addEntry(bundle, file.getName(), file);
+        }
+
+        String classFile = AggregatedBundleActivator.class.getName().replace(".", "/") + ".class";
+        InputStream classStream = BundleAggregatorMojo.class.getClassLoader().getResourceAsStream(classFile);
+        addEntry(bundle, classFile, classStream);
+        bundle.close();
+    }
+
+    private static void addDir(JarOutputStream jos, File root, File dir) throws IOException, FileNotFoundException {
+        for (File file : dir.listFiles()) {
+            if (file.isDirectory()) {
+                addDir(jos, root, file);
+            } else if (file.isFile()) {
+                // getLog().info(file.toString());
+                String uri = root.toURI().relativize(file.toURI()).toString();
+                if ("META-INF/MANIFEST.MF".equals(uri)) {
+                    continue;
+                }
+                addEntry(jos, uri, file);
+            }
+        }
+    }
+
+    private static void addEntry(JarOutputStream jos, String name, File file) throws IOException {
+        FileInputStream in = new FileInputStream(file);
+        addEntry(jos, name, in);
+    }
+
+    private static final byte[] buf = new byte[4096];
+
+    private static void addEntry(JarOutputStream jos, String name, InputStream in) throws IOException {
+        ZipEntry entry = new ZipEntry(name);
+        jos.putNextEntry(entry);
+        for (;;) {
+            int len = in.read(buf);
+            if (len > 0) {
+                jos.write(buf, 0, len);
+            } else {
+                break;
+            }
+        }
+        in.close();
+        jos.closeEntry();
+    }
+
+    private static void generateJar(File root, File jar, Manifest mf) throws IOException {
+        FileOutputStream fos = new FileOutputStream(jar);
+        JarOutputStream jos = mf != null ? new JarOutputStream(fos, mf) : new JarOutputStream(fos);
+        addDir(jos, root, root);
+        jos.close();
+    }
+
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        try {
+            aggregateBundles(rootDirectory, targetBundleFile);
+        } catch (Exception e) {
+            throw new MojoExecutionException(e.getMessage(), e);
+        }
+
+    }
+}

Propchange: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java
URL: http://svn.apache.org/viewvc/tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java?rev=896154&view=auto
==============================================================================
--- tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java (added)
+++ tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java Tue Jan  5 18:00:36 2010
@@ -0,0 +1,331 @@
+/*
+ * 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.tuscany.maven.bundle.plugin;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+/**
+ * Parser for the service descriptors. The syntax of the service declaration is similar with the OSGi
+ * headers with the following exceptions:
+ * <ul>
+ * <li>Tuscany uses , and ; as the separator for attibutes
+ * <li>Tuscany 
+ */
+public class HeaderParser {
+
+    private static final String PATH_SEPARATOR = ","; // OSGi style
+    // private static final String PATH_SEPARATOR = "|";
+
+    private static final String SEGMENT_SEPARATOR = ";"; // OSGi style
+    // private static final String SEGMENT_SEPARATOR = ";,";
+
+    private static final String ATTRIBUTE_SEPARATOR = "=";
+    private static final String DIRECTIVE_SEPARATOR = ":=";
+
+    private static final char QUOTE_CHAR = '"';
+    private static final String QUOTE = "\"";
+
+    // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
+    //            path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static List<HeaderClause> parse(String header) {
+
+        if (header != null) {
+            if (header.length() == 0) {
+                throw new IllegalArgumentException("A header cannot be an empty string.");
+            }
+
+            String[] clauseStrings = parseDelimitedString(header, PATH_SEPARATOR);
+
+            List<HeaderClause> completeList = new ArrayList<HeaderClause>();
+            for (int i = 0; (clauseStrings != null) && (i < clauseStrings.length); i++) {
+                completeList.add(parseClause(clauseStrings[i]));
+            }
+
+            return completeList;
+        }
+
+        return null;
+
+    }
+
+    // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    private static HeaderClause parseClause(String clauseString) throws IllegalArgumentException {
+        // Break string into semi-colon delimited pieces.
+        String[] pieces = parseDelimitedString(clauseString, SEGMENT_SEPARATOR);
+
+        // Count the number of different paths; paths
+        // will not have an '=' in their string. This assumes
+        // that paths come first, before directives and
+        // attributes.
+        int pathCount = 0;
+        for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++) {
+            if (pieces[pieceIdx].indexOf('=') >= 0) {
+                break;
+            }
+            pathCount++;
+        }
+
+        // Create an array of paths.
+        String[] paths = new String[pathCount];
+        System.arraycopy(pieces, 0, paths, 0, pathCount);
+
+        // Parse the directives/attributes.
+        Map<String, String> dirsMap = new HashMap<String, String>();
+        Map<String, String> attrsMap = new HashMap<String, String>();
+        int idx = -1;
+        String sep = null;
+        for (int pieceIdx = pathCount; pieceIdx < pieces.length; pieceIdx++) {
+            // Check if it is a directive.
+            if ((idx = pieces[pieceIdx].indexOf(DIRECTIVE_SEPARATOR)) >= 0) {
+                sep = DIRECTIVE_SEPARATOR;
+            }
+            // Check if it is an attribute.
+            else if ((idx = pieces[pieceIdx].indexOf(ATTRIBUTE_SEPARATOR)) >= 0) {
+                sep = ATTRIBUTE_SEPARATOR;
+            }
+            // It is an error.
+            else {
+                throw new IllegalArgumentException("Not a directive/attribute: " + clauseString);
+            }
+
+            String key = pieces[pieceIdx].substring(0, idx).trim();
+            String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
+
+            // Remove quotes, if value is quoted.
+            if (value.startsWith(QUOTE) && value.endsWith(QUOTE)) {
+                value = value.substring(1, value.length() - 1);
+            }
+
+            // Save the directive/attribute in the appropriate array.
+            if (sep.equals(DIRECTIVE_SEPARATOR)) {
+                // Check for duplicates.
+                if (dirsMap.get(key) != null) {
+                    throw new IllegalArgumentException("Duplicate directive: " + key);
+                }
+                dirsMap.put(key, value);
+            } else {
+                // Check for duplicates.
+                if (attrsMap.get(key) != null) {
+                    throw new IllegalArgumentException("Duplicate attribute: " + key);
+                }
+                attrsMap.put(key, value);
+            }
+        }
+
+        StringBuffer path = new StringBuffer();
+        for (int i = 0; i < paths.length; i++) {
+            path.append(paths[i]);
+            if (i != paths.length - 1) {
+                path.append(';');
+            }
+        }
+
+        HeaderClause descriptor = new HeaderClause();
+        descriptor.text = clauseString;
+        descriptor.value = path.toString();
+        descriptor.valueComponents = paths;
+        descriptor.attributes = attrsMap;
+        descriptor.directives = dirsMap;
+
+        return descriptor;
+    }
+
+    /**
+     * Parses delimited string and returns an array containing the tokens. This
+     * parser obeys quotes, so the delimiter character will be ignored if it is
+     * inside of a quote. This method assumes that the quote character is not
+     * included in the set of delimiter characters.
+     * @param value the delimited string to parse.
+     * @param delim the characters delimiting the tokens.
+     * @return an array of string tokens or null if there were no tokens.
+    **/
+    private static String[] parseDelimitedString(String value, String delim) {
+        if (value == null) {
+            value = "";
+        }
+
+        List<String> list = new ArrayList<String>();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+            boolean isQuote = (c == QUOTE_CHAR);
+
+            if (isDelimiter && ((expecting & DELIMITER) > 0)) {
+                list.add(sb.toString().trim());
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            } else if (isQuote && ((expecting & STARTQUOTE) > 0)) {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            } else if (isQuote && ((expecting & ENDQUOTE) > 0)) {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            } else if ((expecting & CHAR) > 0) {
+                sb.append(c);
+            } else {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+        }
+
+        if (sb.length() > 0) {
+            list.add(sb.toString().trim());
+        }
+
+        return (String[])list.toArray(new String[list.size()]);
+    }
+
+    public static class HeaderClause {
+        private String text;
+        private String value;
+        private String[] valueComponents;
+        private Map<String, String> attributes;
+        private Map<String, String> directives;
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+
+        public String[] getValueComponents() {
+            return valueComponents;
+        }
+
+        public void setValueComponents(String[] valueComponents) {
+            this.valueComponents = valueComponents;
+        }
+
+        public Map<String, String> getAttributes() {
+            return attributes;
+        }
+
+        public Map<String, String> getDirectives() {
+            return directives;
+        }
+
+        public String toString() {
+            String text = null;
+            if (text == null) {
+                StringBuffer buf = new StringBuffer();
+                if (value == null) {
+                    int start = buf.length();
+                    for (int i = 0; i < valueComponents.length; i++) {
+                        if (i != valueComponents.length - 1) {
+                            buf.append(valueComponents[i]).append(';');
+                        } else {
+                            buf.append(valueComponents[i]);
+                        }
+                    }
+                    int end = buf.length();
+                    if (end > start) {
+                        value = buf.substring(start, end);
+                    }
+                }
+                buf.append(value);
+                for (Map.Entry<String, String> e : attributes.entrySet()) {
+                    buf.append(';').append(e.getKey()).append("=\"").append(e.getValue()).append("\"");
+                }
+                for (Map.Entry<String, String> e : directives.entrySet()) {
+                    buf.append(';').append(e.getKey()).append(":=\"").append(e.getValue()).append("\"");
+                }
+                text = buf.toString();
+            }
+            return text;
+        }
+
+    }
+
+    /**
+     * Returns a QName object from a QName expressed as {ns}name
+     * or ns#name.
+     *
+     * @param qname
+     * @return
+     */
+    public static QName getQName(String qname) {
+        if (qname == null) {
+            return null;
+        }
+        qname = qname.trim();
+        if (qname.startsWith("{")) {
+            int h = qname.indexOf('}');
+            if (h != -1) {
+                return new QName(qname.substring(1, h), qname.substring(h + 1));
+            }
+        } else {
+            int h = qname.indexOf('#');
+            if (h != -1) {
+                return new QName(qname.substring(0, h), qname.substring(h + 1));
+            }
+        }
+        return new QName(qname);
+    }
+
+    public static String toHeader(List<HeaderClause> descriptors) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < descriptors.size(); i++) {
+            HeaderClause descriptor = descriptors.get(i);
+            buf.append(descriptor);
+            if (i != descriptors.size() - 1) {
+                buf.append(',');
+            }
+        }
+        return buf.toString();
+    }
+
+    public static String merge(String... headers) {
+        List<HeaderClause> merged = new ArrayList<HeaderClause>();
+        for (String header : headers) {
+            if (header == null || header.length() == 0) {
+                continue;
+            }
+            List<HeaderClause> descriptors = parse(header);
+            merged.addAll(descriptors);
+        }
+        Set<String> values = new HashSet<String>();
+        for (Iterator<HeaderClause> i = merged.iterator(); i.hasNext();) {
+            if (!values.add(i.next().getValue())) {
+                i.remove();
+            }
+        }
+        return toHeader(merged);
+    }
+}

Propchange: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tuscany/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date