You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ri...@apache.org on 2006/05/24 09:42:31 UTC

svn commit: r409086 - in /incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin: Clazz.java Jar.java OsgiJarMojo.java QuotedTokenizer.java

Author: rickhall
Date: Wed May 24 00:42:30 2006
New Revision: 409086

URL: http://svn.apache.org/viewvc?rev=409086&view=rev
Log:
Updating the maven OSGi plugin to incorporate Peter Kriens' modifications
for import-package header generation and more. I built the project and
everything seemed to work fine. (FELIX-74)

Added:
    incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Clazz.java   (with props)
    incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Jar.java   (with props)
    incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/QuotedTokenizer.java   (with props)
Modified:
    incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/OsgiJarMojo.java

Added: incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Clazz.java
URL: http://svn.apache.org/viewvc/incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Clazz.java?rev=409086&view=auto
==============================================================================
--- incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Clazz.java (added)
+++ incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Clazz.java Wed May 24 00:42:30 2006
@@ -0,0 +1,190 @@
+package org.apache.felix.tools.maven.plugin;
+
+import java.io.*;
+import java.util.*;
+
+public class Clazz {
+	static byte	SkipTable[]	= {0, // 0 non existent
+		-1, // 1 CONSTANT_utf8 UTF 8, handled in
+		// method
+		-1, // 2
+		4, // 3 CONSTANT_Integer
+		4, // 4 CONSTANT_Float
+		8, // 5 CONSTANT_Long (index +=2!)
+		8, // 6 CONSTANT_Double (index +=2!)
+		-1, // 7 CONSTANT_Class
+		2, // 8 CONSTANT_String
+		4, // 9 CONSTANT_FieldRef
+		4, // 10 CONSTANT_MethodRef
+		4, // 11 CONSTANT_InterfaceMethodRef
+		4, // 12 CONSTANT_NameAndType
+						};
+
+	Set			imports = new HashSet();
+	String		path;
+	Jar			jar;
+	
+	public Clazz(Jar jar, String path, InputStream in) throws IOException {
+		this.path = path;
+		this.jar = jar;
+		DataInputStream din = new DataInputStream(in);
+		parseClassFile(din);
+	}
+
+
+	void parseClassFile(DataInputStream in)
+			throws IOException {
+		Set classes = new HashSet();
+		Set descriptors = new HashSet();
+		Hashtable pool = new Hashtable();
+		try {
+			int magic = in.readInt();
+			if (magic != 0xCAFEBABE)
+				throw new IOException(
+						"Not a valid class file (no CAFEBABE header)");
+			in.readShort(); // minor version
+			in.readShort(); // major version
+			int count = in.readUnsignedShort();
+			process: for (int i = 1; i < count; i++) {
+				byte tag = in.readByte();
+				switch (tag) {
+					case 0 :
+						break process;
+					case 1 :
+						// CONSTANT_Utf8
+						String name = in.readUTF();
+						pool.put(new Integer(i), name);
+						break;
+					// A Class constant is just a short reference in
+					// the constant pool
+					case 7 :
+						// CONSTANT_Class
+						Integer index = new Integer(in.readShort());
+						classes.add(index);
+						break;
+					// For some insane optimization reason are
+					// the long and the double two entries in the
+					// constant pool. See 4.4.5
+					case 5 :
+						// CONSTANT_Long
+					case 6 :
+						// CONSTANT_Double
+						in.skipBytes(8);
+						i++;
+						break;
+
+					// Interface Method Ref
+					case 12 :
+						in.readShort(); // Name index
+						int descriptorIndex = in.readShort();
+						descriptors.add(new Integer(descriptorIndex));
+						break;
+
+					// We get the skip count for each record type
+					// from the SkipTable. This will also automatically
+					// abort when
+					default :
+						if (tag == 2)
+							throw new IOException("Invalid tag " + tag);
+						in.skipBytes(SkipTable[tag]);
+						break;
+				}
+			}
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+			return;
+		}
+		//
+		// Now iterate over all classes we found and
+		// parse those as well. We skip duplicates
+		//
+
+		for (Iterator e = classes.iterator(); e.hasNext();) {
+			Integer n = (Integer) e.next();
+			String next = (String) pool.get(n);
+			if (next != null) {
+				String normalized = normalize(next);
+				if (normalized != null) {
+                    // For purposes of trying to guess the activator class, we assume
+                    // that any class that references the BundleActivator interface
+                    // must be a BundleActivator implementation.
+					if ( normalized.startsWith("org/osgi/framework/BundleActivator")) {
+						String cname = path.replace('/', '.');
+						cname = cname.substring(0,cname.length()-".class" .length());
+						jar.addActivator(cname);
+					}
+					String pack = getPackage(normalized);
+					if (!pack.startsWith("java."))
+						imports.add(pack);
+				}
+			}
+			else
+				throw new IllegalArgumentException("Invalid class, parent=");
+		}
+		for (Iterator e = descriptors.iterator(); e.hasNext();) {
+			Integer n = (Integer) e.next();
+			String prototype = (String) pool.get(n);
+			if (prototype != null)
+				parseDescriptor(prototype);
+		}
+	}
+
+	void parseDescriptor(String prototype) {
+		addReference(prototype);
+		StringTokenizer st = new StringTokenizer(prototype, "(;)", true);
+		while (st.hasMoreTokens()) {
+			if (st.nextToken().equals("(")) {
+				String token = st.nextToken();
+				while (!token.equals(")")) {
+					addReference(token);
+					token = st.nextToken();
+				}
+				token = st.nextToken();
+				addReference(token);
+			}
+		}
+	}
+
+	private void addReference(String token) {
+		if (token.startsWith("L")) {
+			String clazz = normalize(token.substring(1));
+			if (clazz.startsWith("java/"))
+				return;
+			String pack = getPackage(clazz);
+			imports.add(pack);
+		}
+	}
+
+	static String normalize(String s) {
+		if (s.startsWith("[L"))
+			return normalize(s.substring(2));
+		if (s.startsWith("["))
+			if (s.length() == 2)
+				return null;
+			else
+				return normalize(s.substring(1));
+		if (s.endsWith(";"))
+			return normalize(s.substring(0, s.length() - 1));
+		return s + ".class";
+	}
+
+	static String getPackage(String clazz) {
+		int n = clazz.lastIndexOf('/');
+		if (n < 0)
+			return ".";
+		return clazz.substring(0, n).replace('/', '.');
+	}
+
+
+	public Collection getReferred() {
+		return imports;
+	}
+
+
+	public Object getPath() {
+		return path;
+	}
+
+
+}

Propchange: incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Clazz.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Jar.java
URL: http://svn.apache.org/viewvc/incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Jar.java?rev=409086&view=auto
==============================================================================
--- incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Jar.java (added)
+++ incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Jar.java Wed May 24 00:42:30 2006
@@ -0,0 +1,176 @@
+package org.apache.felix.tools.maven.plugin;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.zip.*;
+
+public class Jar {
+	Map			resources	= new TreeMap();
+	Map			imports		= new HashMap();
+	Map			exports		= new HashMap();
+	Manifest	manifest;
+	boolean		manifestFirst;
+	String		name;
+	Jar			parent;
+	List		activators	= new ArrayList();
+
+	public Jar(Jar parent, String name, InputStream in) throws IOException {
+		this.name = name;
+		this.parent = parent;
+		ZipInputStream jar = new ZipInputStream(in);
+		ZipEntry entry = jar.getNextEntry();
+		boolean first = true;
+		while (entry != null) {
+			String path = entry.getName();
+
+			if (path.endsWith(".class")) {
+				Clazz clazz = new Clazz(this, path, jar);
+				resources.put(path, clazz);
+			}
+			else if (path.endsWith(".jar")) {
+				Jar pool = new Jar(this, path, jar);
+				resources.put(path, pool);
+			}
+			else if (path.endsWith("/packageinfo")
+					&& !path.startsWith("OSGI-OPT")) {
+				String version = parsePackageInfo(jar, exports);
+				resources.put(path, version);
+			}
+			else if (path.equals("META-INF/MANIFEST.MF")) {
+				manifestFirst = first;
+				manifest = new Manifest(jar);
+			}
+			else
+				resources.put(path, null);
+
+			entry = jar.getNextEntry();
+			if (!path.endsWith("/"))
+				first = false;
+		}
+	}
+
+	public Jar(Jar parent, String name, File rootDir) throws IOException {
+		this.name = name;
+		this.parent = parent;
+		traverse(rootDir.getAbsolutePath().length(), rootDir, rootDir.list());
+	}
+
+	void traverse(int root, File dir, String list[]) throws IOException {
+		for (int i = 0; i < list.length; i++) {
+			File sub = new File(dir, list[i]);
+			if (sub.isDirectory())
+				traverse(root, sub, sub.list());
+			else {
+				String path = sub.getAbsolutePath().substring(root + 1)
+						.replace(File.separatorChar, '/');
+				FileInputStream in = new FileInputStream(sub);
+
+				if (path.endsWith(".class")) {
+					Clazz clazz = new Clazz(this, path, in);
+					resources.put(path, clazz);
+				}
+				else if (path.endsWith(".jar")) {
+					Jar pool = new Jar(this, path, in);
+					resources.put(path, pool);
+				}
+				else if (path.endsWith("/packageinfo")
+						&& !path.startsWith("OSGI-OPT")) {
+					String version = parsePackageInfo(in, exports);
+					resources.put(path, version);
+				}
+				else if (path.endsWith("META-INF/MANIFEST.MF")) {
+					manifest = new Manifest(in);
+				}
+				else
+					resources.put(path, null);
+			}
+		}
+	}
+
+	private static String parsePackageInfo(InputStream jar, Map exports)
+			throws IOException {
+		try {
+			byte[] buf = collect(jar, 0);
+			String line = new String(buf);
+			StringTokenizer qt = new StringTokenizer(line, " \r\n\t");
+			if (qt.hasMoreElements()) {
+				qt.nextToken();
+				if (qt.hasMoreElements()) {
+					String version = qt.nextToken();
+					return version;
+				}
+			}
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	/**
+	 * Convenience method to turn an inputstream into a byte array. The method
+	 * uses a recursive algorithm to minimize memory usage.
+	 * 
+	 * @param in stream with data
+	 * @param offset where we are in the stream
+	 * @returns byte array filled with data
+	 */
+	private static byte[] collect(InputStream in, int offset)
+			throws IOException {
+		byte[] result;
+		byte[] buffer = new byte[10000];
+		int size = in.read(buffer);
+		if (size <= 0)
+			return new byte[offset];
+		else
+			result = collect(in, offset + size);
+		System.arraycopy(buffer, 0, result, offset, size);
+		return result;
+	}
+
+	public Manifest getManifest() {
+		return manifest;
+	}
+
+	public Object getEntry(String resource) {
+		return resources.get(resource);
+	}
+
+	public boolean exists(String jarOrDir) {
+		return resources.keySet().contains(jarOrDir);
+	}
+
+	public Set getEntryPaths(String prefix) {
+		Set result = new TreeSet();
+		for (Iterator i = resources.keySet().iterator(); i.hasNext();) {
+			String path = (String) i.next();
+			if (path.startsWith(prefix))
+				result.add(path);
+		}
+		return result;
+	}
+
+	String getName() {
+		return name;
+	}
+
+	public String toString() {
+		return getName();
+	}
+
+	public void addActivator(String path) {
+		if (parent != null)
+			parent.addActivator(path);
+		else {
+			activators.add(path);
+		}
+
+	}
+
+    public boolean containsActivator(String path) {
+        if (parent != null)
+            return parent.containsActivator(path);
+        return activators.contains(path);
+    }
+}

Propchange: incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/Jar.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/OsgiJarMojo.java
URL: http://svn.apache.org/viewvc/incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/OsgiJarMojo.java?rev=409086&r1=409085&r2=409086&view=diff
==============================================================================
--- incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/OsgiJarMojo.java (original)
+++ incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/OsgiJarMojo.java Wed May 24 00:42:30 2006
@@ -17,24 +17,15 @@
 
 package org.apache.felix.tools.maven.plugin;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.maven.archiver.MavenArchiveConfiguration;
-import org.apache.maven.archiver.MavenArchiver;
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.DependencyResolutionRequiredException;
-import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugin.MojoExecutionException;
+import java.io.*;
+import java.util.*;
+
+import org.apache.maven.archiver.*;
+import org.apache.maven.artifact.*;
+import org.apache.maven.plugin.*;
 import org.apache.maven.project.MavenProject;
 import org.codehaus.plexus.archiver.ArchiverException;
-import org.codehaus.plexus.archiver.jar.JarArchiver;
-import org.codehaus.plexus.archiver.jar.ManifestException;
+import org.codehaus.plexus.archiver.jar.*;
 import org.codehaus.plexus.util.FileUtils;
 
 /**
@@ -47,262 +38,609 @@
  * @requiresDependencyResolution runtime
  * @description build an OSGi bundle jar
  */
-public class OsgiJarMojo extends AbstractMojo
-{
-    private static final String[] EMPTY_STRING_ARRAY = {};
-
-    /**
-     * The Maven project.
-     * @parameter expression="${project}"
-     * @required
-     * @readonly
-     */
-    private MavenProject project;
-
-    /**
-     * The directory for the generated JAR.
-     *
-     * @parameter expression="${project.build.directory}"
-     * @required
-     */
-    private String buildDirectory;
-
-    /**
-     * The directory containing generated classes.
-     *
-     * @parameter expression="${project.build.outputDirectory}"
-     * @required
-     * @readonly
-     */
-    private File outputDirectory;
-
-    /**
-     * The name of the generated JAR file.
-     * 
-     * @parameter alias="jarName" expression="${project.build.finalName}"
-     * @required
-     */
-    private String jarName;
-
-    /**
-     * The Jar archiver.
-     *
-     * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
-     * @required
-     */
-    private JarArchiver jarArchiver;
-
-    /**
-     * The maven archive configuration to use.
-     */
-    private MavenArchiveConfiguration archiveConfig = new MavenArchiveConfiguration();
-
-    /**
-     * The comma separated list of tokens to include in the JAR.
-     * Default is '**'.
-     *
-     * @parameter alias="includes"
-     */
-    private String jarSourceIncludes = "**";
-
-    /**
-     * The comma separated list of tokens to exclude from the JAR.
-     *
-     * @parameter alias="excludes"
-     */
-    private String jarSourceExcludes;
-
-    /**
-     * @parameter
-     */
-    private String manifestFile;
-
-    /**
-     * @parameter expression="${org.apache.felix.tools.maven.plugin.OsgiManifest}"
-     */
-    private OsgiManifest osgiManifest;
-
-    /**
-     * Execute this Mojo
-     * 
-     * @throws MojoExecutionException
-     */
-    public void execute() throws MojoExecutionException
-    {
-        File jarFile = new File( buildDirectory, jarName + ".jar" );
-
-        try
-        {
-            performPackaging( jarFile );
-        }
-        catch ( Exception e )
-        {
-            throw new MojoExecutionException( "Error assembling JAR bundle", e );
-        }
-    }
-
-    /**
-     * Generates the JAR bundle file.
-     *
-     * @param  jarFile the target JAR file
-     * @throws IOException
-     * @throws ArchiverException
-     * @throws ManifestException
-     * @throws DependencyResolutionRequiredException
-     */
-    private void performPackaging( File jarFile ) throws IOException, ArchiverException, ManifestException,
-            DependencyResolutionRequiredException, MojoExecutionException
-    {
-        getLog().info( "Generating JAR bundle " + jarFile.getAbsolutePath() );
-
-        MavenArchiver archiver = new MavenArchiver();
-
-        archiver.setArchiver( jarArchiver );
-        archiver.setOutputFile( jarFile );
-
-        addManifestFile();
-        addManifestEntries();
-
-        addBundleClasspath();
-        addBundleVersion();
-
-        jarArchiver.addDirectory( outputDirectory, getIncludes(), getExcludes() );
-
-        archiver.createArchive( project, archiveConfig );
-
-        project.getArtifact().setFile( jarFile );
-    }
-
-    /**
-     * TODO: Decide if we accept merging of entire manifest.mf files
-     * Here's a big question to make a final decision at some point: Do accept
-     * merging of manifest entries located in some file somewhere in the project
-     * directory?  If so, do we allow both file and configuration based entries
-     * to be specified simultaneously and how do we merge these?
-     */
-    private void addManifestFile()
-    {
-        if ( manifestFile != null )
-        {
-            File file = new File( project.getBasedir().getAbsolutePath(), manifestFile );
-            getLog().info( "Manifest file: " + file.getAbsolutePath() + " will be used" );
-            archiveConfig.setManifestFile( file );
-        }
-        else
-        {
-            getLog().info( "No manifest file specified. Default will be used." );
-        }
-    }
-
-    /**
-     * Look for any OSGi specified manifest entries in the maven-osgi-plugin configuration
-     * section of the POM.  If we find some, then add them to the target artifact's manifest.
-     */
-    private void addManifestEntries()
-    {
-        if ( osgiManifest != null && osgiManifest.getEntries().size() > 0 )
-        {
-            Map entries = osgiManifest.getEntries();
-
-            getLog().info( "Bundle manifest will be modified with the following entries: " + entries.toString() );
-            archiveConfig.addManifestEntries( entries );
-        }
-        else
-        {
-            getLog().info( "No OSGi bundle manifest entries have been specified in the POM." );
-        }
-    }
-
-    /**
-     * We are going to iterate through the POM's specified JAR dependencies.  If a dependency
-     * has a scope of either RUNTIME or COMPILE, then we'll JAR them up inside the
-     * OSGi bundle artifact.  We will then add the Bundle-Classpath manifest entry.
-     */
-    private void addBundleClasspath() throws MojoExecutionException
-    {
-        StringBuffer bundleClasspath = new StringBuffer();
-        Set artifacts = project.getArtifacts();
-
-        for ( Iterator it = artifacts.iterator(); it.hasNext(); )
-        {
-            Artifact artifact = (Artifact) it.next();
-            if ( !Artifact.SCOPE_PROVIDED.equals( artifact.getScope() )
-                    && !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
-            {
-                String type = artifact.getType();
-
-                if ( "jar".equals( type ) )
-                {
-                    File depFile = artifact.getFile();
-
-                    try
-                    {
-                        FileUtils.copyFileToDirectory( depFile, outputDirectory );
-
-                        if ( bundleClasspath.length() == 0 )
-                        {
-                            bundleClasspath.append( "." );
-                        }
-
-                        bundleClasspath.append( "," + artifact.getFile().getName() );
-                    }
-                    catch ( Exception e )
-                    {
-                        String errmsg = "Error copying " + depFile.getAbsolutePath() + " to "
-                                + outputDirectory.getAbsolutePath();
-                        throw new MojoExecutionException( errmsg, e );
-                    }
-                }
-            }
-        }
-
-        String finalPath = bundleClasspath.toString();
-
-        if ( finalPath.length() != 0 )
-        {
-            archiveConfig.addManifestEntry( "Bundle-Classpath", finalPath );
-        }
-    }
-
-    /**
-     * Auto-set the bundle version.
-     */
-    private void addBundleVersion()
-    {
-        // Maven uses a '-' to separate the version qualifier,
-        // while OSGi uses a '.', so we need to convert to a '.'
-        StringBuffer sb = new StringBuffer(project.getVersion());
-        if (sb.indexOf("-") >= 0)
-        {
-            sb.setCharAt(sb.indexOf("-"), '.');
-        }
-        archiveConfig.addManifestEntry( "Bundle-Version", sb.toString() );
-    }
-
-    /**
-     * Returns a string array of the includes to be used when assembling/copying the war.
-     *
-     * @return an array of tokens to include
-     */
-    private String[] getIncludes()
-    {
-        return new String[] { jarSourceIncludes };
-    }
-
-    /**
-     * Returns a string array of the excludes to be used when assembling/copying the jar.
-     *
-     * @return an array of tokens to exclude
-     */
-    private String[] getExcludes()
-    {
-        List excludeList = new ArrayList( FileUtils.getDefaultExcludesAsList() );
-
-        if ( jarSourceExcludes != null && !"".equals( jarSourceExcludes ) )
-        {
-            excludeList.add( jarSourceExcludes );
-        }
+public class OsgiJarMojo extends AbstractMojo {
+	private static final String[]		EMPTY_STRING_ARRAY		= {};
+
+	int									bundleManifestVersion	= 1;
+
+	/**
+	 * The Maven project.
+	 * 
+	 * @parameter expression="${project}"
+	 * @required
+	 * @readonly
+	 */
+	private MavenProject				project;
+
+	/**
+	 * The directory for the generated JAR.
+	 * 
+	 * @parameter expression="${project.build.directory}"
+	 * @required
+	 */
+	private String						buildDirectory;
+
+	/**
+	 * The directory containing generated classes.
+	 * 
+	 * @parameter expression="${project.build.outputDirectory}"
+	 * @required
+	 * @readonly
+	 */
+	private File						outputDirectory;
+
+	/**
+	 * The name of the generated JAR file.
+	 * 
+	 * @parameter alias="jarName" expression="${project.build.finalName}"
+	 * @required
+	 */
+	private String						jarName;
+
+	/**
+	 * The Jar archiver.
+	 * 
+	 * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
+	 * @required
+	 */
+	private JarArchiver					jarArchiver;
+
+	/**
+	 * The maven archive configuration to use.
+	 */
+	private MavenArchiveConfiguration	archiveConfig			= new MavenArchiveConfiguration();
+
+	/**
+	 * The comma separated list of tokens to include in the JAR. Default is
+	 * '**'.
+	 * 
+	 * @parameter alias="includes"
+	 */
+	private String						jarSourceIncludes		= "**";
+
+	/**
+	 * The comma separated list of tokens to exclude from the JAR.
+	 * 
+	 * @parameter alias="excludes"
+	 */
+	private String						jarSourceExcludes;
+
+	/**
+	 * @parameter
+	 */
+	private String						manifestFile;
+
+	/**
+	 * @parameter expression="${org.apache.felix.tools.maven.plugin.OsgiManifest}"
+	 */
+	private OsgiManifest				osgiManifest;
+
+	/**
+	 * Execute this Mojo
+	 * 
+	 * @throws MojoExecutionException
+	 */
+	public void execute() throws MojoExecutionException {
+		File jarFile = new File(buildDirectory, jarName + ".jar");
+
+		try {
+			performPackaging(jarFile);
+		}
+		catch (Exception e) {
+			throw new MojoExecutionException("Error assembling JAR bundle", e);
+		}
+	}
+
+	/**
+	 * Generates the JAR bundle file.
+	 * 
+	 * @param jarFile the target JAR file
+	 * @throws IOException
+	 * @throws ArchiverException
+	 * @throws ManifestException
+	 * @throws DependencyResolutionRequiredException
+	 */
+	private void performPackaging(File jarFile) throws IOException,
+			ArchiverException, ManifestException,
+			DependencyResolutionRequiredException, MojoExecutionException {
+
+		verifyDeclaredBundleManifestVersion();
+
+		getLog().info("Generating JAR bundle " + jarFile.getAbsolutePath());
+
+		MavenArchiver archiver = new MavenArchiver();
+
+		archiver.setArchiver(jarArchiver);
+		archiver.setOutputFile(jarFile);
+
+		addManifestFile();
+		addManifestEntries();
+
+		// Add the JARs that were specified in the POM
+		// as "not" provided
+		addEmbeddedJars();
+		addBundleVersion();
+
+		jarArchiver.addDirectory(outputDirectory, getIncludes(), getExcludes());
+
+		// Parse the output directory as if it was a JAR file.
+		// This creates special entries for classes, packageinfo
+		// and embedded JAR files (which are parsed as well).
+		Jar mainJar = new Jar(null, jarName, outputDirectory);
+
+		// Calculate the Bundle Classpath from the embedded
+		// JAR files. We hardcode the bcp as ., <embedded jars>
+		// TODO we add all the found JARs to the Bcp, maybe we
+		// should look if they are needed by traversing the imports.
+		List bundleClassPath = getBundleClassPath(mainJar);
+		bundleClassPath.add(0, ".");
+		createBundleClasspathHeader(bundleClassPath);
+
+		// Calculate the exports (contained) and imports (referred)
+		// The bundleClassPath contains the JARs in the right order
+		Set contained = new HashSet(); // package name
+		Set referred = new HashSet(); // package name
+		Map uses = new HashMap(); // package name => Set of package name
+
+		// Iterate over the bundle class path and calculate the contained
+		// and referred packages as well as the uses.
+		for (Iterator i = bundleClassPath.iterator(); i.hasNext();) {
+			String path = (String) i.next();
+			Jar jar = path.equals(".") ? mainJar : (Jar) mainJar.resources
+					.get(path);
+			analyzeJar(jar, contained, referred, uses);
+		}
+
+		referred.removeAll(contained);
+
+		Map exports = parseHeader(osgiManifest.getExportPackage());
+		Map imports = parseHeader(osgiManifest.getImportPackage());
+		Map dynamicImports = parseHeader(osgiManifest.getDynamicImportPackage());
+
+		if (dynamicImports != null) {
+			// Remove any dynamic imports from the referred set.
+			referred = new HashSet(referred);
+			referred.removeAll(dynamicImports.keySet());
+		}
+
+		if (exports != null) {
+			verifyDeclaredExports(exports, contained);
+			createExportHeader(exports, uses);
+		}
+
+		// If the POM file contains an import declaration,
+		// we verify its validity. Otherwise, we generate the
+		// import package header from the referred. Exports
+		// are added to automatically imports for R4 bundles.
+		if (imports == null) {
+			createImportHeader(referred, exports == null ? new HashSet()
+					: exports.keySet());
+		}
+		else {
+			verifyDeclaredImports(referred, imports);
+		}
+
+		verifyBundleActivator(mainJar);
+		
+		archiver.createArchive(project, archiveConfig);
+		project.getArtifact().setFile(jarFile);
+	}
+
+	private void verifyBundleActivator(Jar mainJar) {
+		String ba = osgiManifest.getBundleActivator();
+		if (ba == null || ba.trim().length() == 0) {
+			switch ( mainJar.activators.size() ) {
+				case 0: break;
+				case 1: archiveConfig.addManifestEntry("Bundle-Activator", mainJar.activators.get(0));
+				break;
+				default:
+					getLog().info("[OSGi] No Bundle-Activator specified and multiple found" );
+				break;
+			}
+		}
+		else {
+			if( ! mainJar.activators.contains(ba))
+				getLog().warn("[OSGi] UNABLE TO VERIFY BUNDLE ACTIVATOR: " + ba);
+		}
+	}
+
+	private void createBundleClasspathHeader(List bundleClassPath) {
+		StringBuffer sb = new StringBuffer();
+		String del = ".,";
+		for (Iterator i = bundleClassPath.iterator(); i.hasNext();) {
+			sb.append(del);
+			sb.append(i.next());
+			del = ",";
+		}
+		if (sb.length() > 0)
+			archiveConfig.addManifestEntry("Bundle-Classpath", sb.toString());
+	}
+
+	/**
+	 * Iterate over the declared exports from the POM, verify that they are
+	 * present, add the uses clause if necessary and finally add the manifest
+	 * entry.
+	 * 
+	 * @param contained Set of contained packages
+	 * @param exports Map with the export clauses from the POM
+	 * @param uses Map with use clauses
+	 * @throws MojoExecutionException
+	 */
+	void verifyDeclaredExports(Map exports, Set contained)
+			throws MojoExecutionException {
+		Set declaredExports = exports.keySet();
+		for (Iterator i = declaredExports.iterator(); i.hasNext();) {
+			String pack = (String) i.next();
+			if (!contained.contains(pack)) {
+				getLog()
+						.error("[OSGi] EXPORTED PACKAGE NOT IN BUNDLE: " + pack);
+				throw new MojoExecutionException(
+						"Exported Package not found in bundle or JARs on bundle class path "
+								+ pack);
+			}
+
+		}
+	}
+
+	/**
+	 * Print out the export headers after adding the uses clause.
+	 * 
+	 * @param exports
+	 * @param uses
+	 * @throws MojoExecutionException
+	 */
+	void createExportHeader(Map exports, Map uses)
+			throws MojoExecutionException {
+		if (exports.size() > 0) {
+			Set declaredExports = exports.keySet();
+			for (Iterator i = declaredExports.iterator(); i.hasNext();) {
+				String pack = (String) i.next();
+				Map clause = (Map) exports.get(pack);
+
+				if (bundleManifestVersion >= 2) {
+					Set t = (Set) uses.get(pack);
+					if (t != null && !t.isEmpty()) {
+						StringBuffer sb = new StringBuffer();
+						String del = "\"";
+						for (Iterator u = t.iterator(); u.hasNext();) {
+							String usedPackage = (String) u.next();
+							if (!usedPackage.equals(pack)) {
+								sb.append(del);
+								sb.append(usedPackage);
+								del = ",";
+							}
+						}
+						sb.append("\"");
+						clause.put("uses:", sb.toString());
+					}
+				}
+			}
+			archiveConfig.addManifestEntry(
+					"Export-Package",
+					printClauses(exports));
+		}
+	}
+
+	/**
+	 * Verify that the declared imports match the referred packages.
+	 * 
+	 * @param referred referred package
+	 * @param imports imported packages from POM
+	 * @throws MojoExecutionException
+	 */
+	void verifyDeclaredImports(Set referred, Map imports)
+			throws MojoExecutionException {
+		Set declaredImports = imports.keySet();
+		Set test = new HashSet(referred);
+		test.removeAll(declaredImports);
+		for (Iterator m = test.iterator(); m.hasNext();) {
+			Object o = m.next();
+			getLog().error("[OSGi] MISSING IMPORT: " + o);
+			throw new MojoExecutionException("Missing Import " + o);
+		}
+
+		test = new HashSet(declaredImports);
+		test.removeAll(referred);
+		for (Iterator m = test.iterator(); m.hasNext();) {
+			getLog().warn("[OSGi] SUPERFLUOUS IMPORT: " + m.next());
+			getLog()
+					.warn(
+							"[OSGi] Removing the POM Import-Package element will automatically generate the import clauses");
+		}
+	}
+
+	/**
+	 * Standard OSGi header parser. This parser can handle the format clauses
+	 * ::= clause ( ',' clause ) + clause ::= name ( ';' name ) (';' key '='
+	 * value )
+	 * 
+	 * This is mapped to a Map { name => Map { attr|directive => value } }
+	 * 
+	 * @param value
+	 * @return
+	 * @throws MojoExecutionException
+	 */
+	static Map parseHeader(String value) throws MojoExecutionException {
+		if (value == null || value.trim().length() == 0)
+			return null;
+
+		Map result = new HashMap();
+		QuotedTokenizer qt = new QuotedTokenizer(value, ";=,");
+		char del;
+		do {
+			boolean hadAttribute = false;
+			Map clause = new HashMap();
+			List aliases = new ArrayList();
+			aliases.add(qt.nextToken());
+			del = qt.getSeparator();
+			while (del == ';') {
+				String adname = qt.nextToken();
+				if (qt.getSeparator() != '=') {
+					if (hadAttribute)
+						throw new MojoExecutionException(
+								"Header contains name field after attribute or directive: "
+										+ adname + " from " + value);
+					aliases.add(adname);
+				}
+				else {
+					String advalue = qt.nextToken();
+					clause.put(adname, advalue);
+					del = qt.getSeparator();
+					hadAttribute = true;
+				}
+			}
+			for (Iterator i = aliases.iterator(); i.hasNext();) {
+				result.put(i.next(), clause);
+			}
+		} while (del == ',');
+		return result;
+	}
+
+	/**
+	 * Create the import header, taking into account R4 automatic import clauses
+	 * for the exports.
+	 * 
+	 * @param referred
+	 * @param contained
+	 */
+	void createImportHeader(Set referred, Set contained) {
+		if (referred.isEmpty())
+			return;
+
+		referred = new TreeSet(referred);
+
+		if (bundleManifestVersion > 1) {
+			referred.addAll(contained);
+		}
+
+		StringBuffer sb = new StringBuffer();
+		String del = "";
+
+		for (Iterator i = referred.iterator(); i.hasNext();) {
+			sb.append(del);
+			sb.append(i.next());
+			del = ", ";
+		}
+		archiveConfig.addManifestEntry("Import-Package", sb.toString());
+	}
+
+	/**
+	 * Calculate the bundle class path based on the list of JARs in our bundle.
+	 * This list includes outselves. We also calculate the Bundle-Classpath
+	 * header (a bit clumsy) This is a bit cheap, so maybe this needs to be
+	 * changed TODO
+	 * 
+	 * @param mainJar
+	 * @param sb
+	 * @return
+	 */
+	List getBundleClassPath(Jar mainJar) {
+		List result = new ArrayList();
+		for (Iterator i = mainJar.resources.keySet().iterator(); i.hasNext();) {
+			String path = (String) i.next();
+			Object resource = mainJar.resources.get(path);
+			if (resource instanceof Jar) {
+				result.add(path);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * We traverse through al the classes that we can find and calculate the
+	 * contained and referred set and uses.
+	 * 
+	 * @param jar
+	 * @param contained
+	 * @param referred
+	 * @param uses
+	 */
+	void analyzeJar(Jar jar, Set contained, Set referred, Map uses) {
+		String prefix = "";
+		Set set = jar.getEntryPaths(prefix);
+		for (Iterator r = set.iterator(); r.hasNext();) {
+			String path = (String) r.next();
+			Object resource = jar.getEntry(path);
+			if (resource instanceof Clazz) {
+				Clazz clazz = (Clazz) resource;
+				String pathOfClass = path.substring(prefix.length());
+				String pack = Clazz.getPackage(pathOfClass);
+				contained.add(pack);
+				referred.addAll(clazz.getReferred());
+
+				// Add all the used packages
+				// to this package
+				Set t = (Set) uses.get(pack);
+				if (t == null)
+					uses.put(pack, t = new HashSet());
+				t.addAll(clazz.getReferred());
+				t.remove(pack);
+			}
+		}
+	}
+
+	/**
+	 * Print a standard Map based OSGi header.
+	 * 
+	 * @param exports map { name => Map { attribute|directive => value } }
+	 * @return the clauses
+	 */
+
+	String printClauses(Map exports) {
+		StringBuffer sb = new StringBuffer();
+		String del = "";
+		for (Iterator i = exports.keySet().iterator(); i.hasNext();) {
+			String name = (String) i.next();
+			Map map = (Map) exports.get(name);
+			sb.append(del);
+			sb.append(name);
+
+			for (Iterator j = map.keySet().iterator(); j.hasNext();) {
+				String key = (String) j.next();
+				String value = (String) map.get(key);
+				sb.append(";");
+				sb.append(key);
+				sb.append("=");
+				sb.append(value);
+			}
+			del = ", ";
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Check if the BundleManifest version is set correctly, base the manifest
+	 * version on it.
+	 * 
+	 * @throws MojoExecutionException
+	 */
+	void verifyDeclaredBundleManifestVersion() throws MojoExecutionException {
+		String mfv = osgiManifest.getBundleManifestVersion();
+		if (mfv != null && mfv.trim().length() != 0) {
+			try {
+				bundleManifestVersion = Integer.parseInt(mfv);
+				if (bundleManifestVersion != 2)
+					throw new MojoExecutionException(
+							"Bundle-ManifestVersion must be 2, it is " + mfv);
+			}
+			catch (Exception e) {
+				throw new MojoExecutionException(
+						"Bundle-ManifestVersion must be an integer: " + mfv);
+			}
+		}
+	}
+
+	/**
+	 * TODO: Decide if we accept merging of entire manifest.mf files Here's a
+	 * big question to make a final decision at some point: Do accept merging of
+	 * manifest entries located in some file somewhere in the project directory?
+	 * If so, do we allow both file and configuration based entries to be
+	 * specified simultaneously and how do we merge these?
+	 */
+	private void addManifestFile() {
+		if (manifestFile != null) {
+			File file = new File(project.getBasedir().getAbsolutePath(),
+					manifestFile);
+			getLog().info(
+					"Manifest file: " + file.getAbsolutePath()
+							+ " will be used");
+			archiveConfig.setManifestFile(file);
+		}
+		else {
+			getLog().info("No manifest file specified. Default will be used.");
+		}
+	}
+
+	/**
+	 * Look for any OSGi specified manifest entries in the maven-osgi-plugin
+	 * configuration section of the POM. If we find some, then add them to the
+	 * target artifact's manifest.
+	 */
+	private void addManifestEntries() {
+		if (osgiManifest != null && osgiManifest.getEntries().size() > 0) {
+			Map entries = osgiManifest.getEntries();
+
+			getLog().info(
+					"Bundle manifest will be modified with the following entries: "
+							+ entries.toString());
+			archiveConfig.addManifestEntries(entries);
+		}
+		else {
+			getLog()
+					.info(
+							"No OSGi bundle manifest entries have been specified in the POM.");
+		}
+	}
+
+	/**
+	 * We are going to iterate through the POM's specified JAR dependencies. If
+	 * a dependency has a scope of either RUNTIME or COMPILE, then we'll JAR
+	 * them up inside the OSGi bundle artifact. We will then add the
+	 * Bundle-Classpath manifest entry.
+	 */
+	private void addEmbeddedJars() throws MojoExecutionException {
+		Set artifacts = project.getArtifacts();
+
+		for (Iterator it = artifacts.iterator(); it.hasNext();) {
+			Artifact artifact = (Artifact) it.next();
+			if (!Artifact.SCOPE_PROVIDED.equals(artifact.getScope())
+					&& !Artifact.SCOPE_TEST.equals(artifact.getScope())) {
+				String type = artifact.getType();
+
+				if ("jar".equals(type)) {
+					File depFile = artifact.getFile();
+
+					try {
+						FileUtils.copyFileToDirectory(depFile, outputDirectory);
+
+					}
+					catch (Exception e) {
+						String errmsg = "Error copying "
+								+ depFile.getAbsolutePath() + " to "
+								+ outputDirectory.getAbsolutePath();
+						throw new MojoExecutionException(errmsg, e);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Auto-set the bundle version.
+	 */
+	private void addBundleVersion() {
+		// Maven uses a '-' to separate the version qualifier,
+		// while OSGi uses a '.', so we need to convert to a '.'
+		StringBuffer sb = new StringBuffer(project.getVersion());
+		if (sb.indexOf("-") >= 0) {
+			sb.setCharAt(sb.indexOf("-"), '.');
+		}
+		archiveConfig.addManifestEntry("Bundle-Version", sb.toString());
+	}
+
+	/**
+	 * Returns a string array of the includes to be used when assembling/copying
+	 * the war.
+	 * 
+	 * @return an array of tokens to include
+	 */
+	private String[] getIncludes() {
+		return new String[] {jarSourceIncludes};
+	}
+
+	/**
+	 * Returns a string array of the excludes to be used when assembling/copying
+	 * the jar.
+	 * 
+	 * @return an array of tokens to exclude
+	 */
+	private String[] getExcludes() {
+		List excludeList = new ArrayList(FileUtils.getDefaultExcludesAsList());
+
+		if (jarSourceExcludes != null && !"".equals(jarSourceExcludes)) {
+			excludeList.add(jarSourceExcludes);
+		}
 
-        return (String[]) excludeList.toArray( EMPTY_STRING_ARRAY );
-    }
+		return (String[]) excludeList.toArray(EMPTY_STRING_ARRAY);
+	}
 }

Added: incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/QuotedTokenizer.java
URL: http://svn.apache.org/viewvc/incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/QuotedTokenizer.java?rev=409086&view=auto
==============================================================================
--- incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/QuotedTokenizer.java (added)
+++ incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/QuotedTokenizer.java Wed May 24 00:42:30 2006
@@ -0,0 +1,114 @@
+package org.apache.felix.tools.maven.plugin;
+
+import java.util.*;
+
+public class QuotedTokenizer {
+	String	string;
+	int		index				= 0;
+	String	separators;
+	boolean	returnTokens;
+	boolean	ignoreWhiteSpace	= true;
+	String	peek;
+	char	separator;
+
+	public QuotedTokenizer(String string, String separators, boolean returnTokens ) {
+		if ( string == null )
+			throw new IllegalArgumentException("string argument must be not null");
+		this.string = string;
+		this.separators = separators;
+		this.returnTokens = returnTokens;
+	}
+	public QuotedTokenizer(String string, String separators) {
+		this(string,separators,false);
+	}
+
+	public String nextToken(String separators) {
+		separator = 0;
+		if ( peek != null ) {
+			String tmp = peek;
+			peek = null;
+			return tmp;
+		}
+		
+		if ( index == string.length())
+			return null;
+		
+		StringBuffer sb = new StringBuffer();
+
+		while (index < string.length()) {
+			char c = string.charAt(index++);
+
+			if ( Character.isWhitespace(c)) {
+				if ( index == string.length())
+					break;
+				else
+					continue;
+			}
+			
+			if (separators.indexOf(c) >= 0) {
+				if (returnTokens)
+					peek = Character.toString(c);
+				else
+					separator = c;
+				break;
+			}
+
+			switch (c) {
+				case '"' :
+				case '\'' :
+					quotedString(sb, c);
+					break;
+
+				default :
+					sb.append(c);
+			}
+		}
+		String result = sb.toString().trim();
+		if ( result.length()==0 && index==string.length())
+			return null;
+		return result;
+	}
+
+	public String nextToken() {
+		return nextToken(separators);
+	}
+
+	private void quotedString(StringBuffer sb, char c) {
+		char quote = c;
+		while (index < string.length()) {
+			c = string.charAt(index++);
+			if (c == quote)
+				break;
+			if (c == '\\' && index < string.length()
+					&& string.charAt(index + 1) == quote)
+				c = string.charAt(index++);
+			sb.append(c);
+		}
+	}
+
+	public String[] getTokens() {
+		return getTokens(0);
+	}
+
+	private String [] getTokens(int cnt){
+		String token = nextToken();
+		if ( token == null ) 
+			return new String[cnt];
+		
+		String result[] = getTokens(cnt+1);
+		result[cnt]=token;
+		return result;
+	}
+
+	public char getSeparator() { return separator; }
+	
+	public List getTokenSet() {
+		List list = new ArrayList();
+		String token = nextToken();
+		while ( token != null ) {
+			list.add(token);
+			token = nextToken();
+		}
+		return list;
+	}
+}

Propchange: incubator/felix/trunk/tools/maven2/maven-osgi-plugin/src/main/java/org/apache/felix/tools/maven/plugin/QuotedTokenizer.java
------------------------------------------------------------------------------
    svn:eol-style = native