Temporarily include bndlib 1.47 for testing purposes (not yet on central)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1185095 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/ConsumerType.java b/bundleplugin/src/main/java/aQute/bnd/annotation/ConsumerType.java
new file mode 100644
index 0000000..cbbc17b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/ConsumerType.java
@@ -0,0 +1,14 @@
+package aQute.bnd.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Adding this annotation to a type in an API package indicates the the owner of
+ * that package will not change this interface in a minor update. Any backward
+ * compatible change to this interface requires a major update of the version of
+ * this package.
+ * 
+ */
+@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface ConsumerType {
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/Export.java b/bundleplugin/src/main/java/aQute/bnd/annotation/Export.java
new file mode 100644
index 0000000..27ea389
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/Export.java
@@ -0,0 +1,30 @@
+package aQute.bnd.annotation;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.PACKAGE)
+public @interface Export {
+    String MANDATORY = "mandatory";
+    String OPTIONAL  = "optional";
+    String USES      = "uses";
+    String EXCLUDE   = "exclude";
+    String INCLUDE   = "include";
+
+    String[] mandatory() default "";
+
+    String[] optional() default "";
+
+    Class<?>[] exclude() default Object.class;
+
+    Class<?>[] include() default Object.class;
+    
+    /**
+     * Use {@link @Version} annotation instead
+     * @return
+     */
+    @Deprecated()
+    String version() default "";
+    
+    
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/ProviderType.java b/bundleplugin/src/main/java/aQute/bnd/annotation/ProviderType.java
new file mode 100644
index 0000000..15ee17e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/ProviderType.java
@@ -0,0 +1,19 @@
+package aQute.bnd.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This type is provided for convenience because it is the default. A change 
+ * in a provider type (that is all except Consumer types) can be changed with
+ * only a minor update to the package API version number.
+ * 
+ * This interface is similar to the Eclipse @noextend and @noimplement annotations.
+ * 
+ * 
+ * 
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+public @interface ProviderType {
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/UsePolicy.java b/bundleplugin/src/main/java/aQute/bnd/annotation/UsePolicy.java
new file mode 100644
index 0000000..36f0eaf
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/UsePolicy.java
@@ -0,0 +1,20 @@
+package aQute.bnd.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation can be applied to interface where an implementation should be
+ * treated as a use policy, not an implementation policy. Many package have
+ * interfaces that are very stable and can be maintained backward compatible for
+ * implementers during minor changes. For example, in Event Admin, the
+ * EventAdmin implementers should follow the minor version, e.g. [1.1,1.2), however,
+ * an implementer of EventHandler should not require such a small range. Therefore
+ * an interface like EventHandler should use this anotation.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+@Deprecated
+public @interface UsePolicy {
+    String RNAME = "LaQute/bnd/annotation/UsePolicy;";
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/Version.java b/bundleplugin/src/main/java/aQute/bnd/annotation/Version.java
new file mode 100644
index 0000000..fae42b1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/Version.java
@@ -0,0 +1,9 @@
+package aQute.bnd.annotation;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.PACKAGE})
+public @interface Version {
+	String value();
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/Activate.java b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Activate.java
new file mode 100644
index 0000000..52284a0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Activate.java
@@ -0,0 +1,10 @@
+package aQute.bnd.annotation.component;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.METHOD)
+public @interface Activate {
+    String RNAME = "LaQute/bnd/annotation/component/Activate;";
+    
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/Attribute.java b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Attribute.java
new file mode 100644
index 0000000..f089b75
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Attribute.java
@@ -0,0 +1,10 @@
+package aQute.bnd.annotation.component;
+
+public @interface Attribute {
+	class C {}
+	
+	String name() default "";
+	String description() default "";
+	String[] options();
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/Component.java b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Component.java
new file mode 100644
index 0000000..1fb39d6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Component.java
@@ -0,0 +1,38 @@
+package aQute.bnd.annotation.component;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface Component {
+	String	RNAME					= "LaQute/bnd/annotation/component/Component;";
+	String	PROVIDE					= "provide";
+	String	NAME					= "name";
+	String	FACTORY					= "factory";
+	String	SERVICEFACTORY			= "servicefactory";
+	String	IMMEDIATE				= "immediate";
+	String	CONFIGURATION_POLICY	= "configurationPolicy";
+	String	ENABLED					= "enabled";
+	String	PROPERTIES				= "properties";
+	String	VERSION					= "version";
+	String	DESIGNATE				= "designate";
+	String	DESIGNATE_FACTORY		= "designateFactory";
+
+	String name() default "";
+
+	Class<?>[] provide() default Object.class;
+
+	String factory() default "";
+
+	boolean servicefactory() default false;
+
+	boolean enabled() default true;
+
+	boolean immediate() default false;
+
+	ConfigurationPolicy configurationPolicy() default ConfigurationPolicy.optional;
+
+	String[] properties() default {};
+
+	Class<?> designate() default Object.class;
+
+	Class<?> designateFactory() default Object.class;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/ConfigurationPolicy.java b/bundleplugin/src/main/java/aQute/bnd/annotation/component/ConfigurationPolicy.java
new file mode 100644
index 0000000..7651557
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/ConfigurationPolicy.java
@@ -0,0 +1,5 @@
+package aQute.bnd.annotation.component;
+
+public enum ConfigurationPolicy {
+    optional, require, ignore;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/Deactivate.java b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Deactivate.java
new file mode 100644
index 0000000..5858ea0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Deactivate.java
@@ -0,0 +1,10 @@
+package aQute.bnd.annotation.component;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.METHOD)
+public @interface Deactivate {
+    String RNAME = "LaQute/bnd/annotation/component/Deactivate;";
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/Modified.java b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Modified.java
new file mode 100644
index 0000000..655a535
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Modified.java
@@ -0,0 +1,10 @@
+package aQute.bnd.annotation.component;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.METHOD)
+public @interface Modified {
+    String RNAME = "LaQute/bnd/annotation/component/Modified;";
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/Reference.java b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Reference.java
new file mode 100644
index 0000000..58894dd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/Reference.java
@@ -0,0 +1,33 @@
+package aQute.bnd.annotation.component;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.METHOD)
+public @interface Reference {
+    String RNAME    = "LaQute/bnd/annotation/component/Reference;";
+    String NAME     = "name";
+    String SERVICE  = "service";
+    String OPTIONAL = "optional";
+    String MULTIPLE = "multiple";
+    String DYNAMIC  = "dynamic";
+    String TARGET   = "target";
+    String TYPE     = "type";
+    String UNBIND   = "unbind";
+
+    String name() default "";
+
+    Class<?> service() default Object.class;
+
+    boolean optional() default false;
+
+    boolean multiple() default false;
+
+    boolean dynamic() default false;
+
+    String target() default "";
+    
+    String unbind() default "";
+
+    char type() default 0;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/component/packageinfo b/bundleplugin/src/main/java/aQute/bnd/annotation/component/packageinfo
new file mode 100644
index 0000000..ec0efd4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/component/packageinfo
@@ -0,0 +1 @@
+version 1.43.1
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/Configurable.java b/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/Configurable.java
new file mode 100644
index 0000000..6dcf614
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/Configurable.java
@@ -0,0 +1,262 @@
+package aQute.bnd.annotation.metatype;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+public class Configurable<T> {
+
+	
+	
+	
+	public static <T> T createConfigurable(Class<T> c, Map<?, ?> properties) {
+		Object o = Proxy.newProxyInstance(c.getClassLoader(), new Class<?>[] { c },
+				new ConfigurableHandler(properties, c.getClassLoader()));
+		return c.cast(o);
+	}
+
+	public static <T> T createConfigurable(Class<T> c, Dictionary<?, ?> properties) {
+		Map<Object,Object> alt = new HashMap<Object,Object>();
+		for( Enumeration<?> e = properties.keys(); e.hasMoreElements(); ) {
+			Object key = e.nextElement();
+			alt.put(key, properties.get(key));
+		}
+		return createConfigurable(c, alt);
+	}
+
+	static class ConfigurableHandler implements InvocationHandler {
+		final Map<?, ?>	properties;
+		final ClassLoader			loader;
+
+		ConfigurableHandler(Map<?, ?> properties, ClassLoader loader) {
+			this.properties = properties;
+			this.loader = loader;
+		}
+
+		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+			Meta.AD ad = method.getAnnotation(Meta.AD.class);
+			String id = Configurable.mangleMethodName(method.getName());
+
+			if (ad != null && !ad.id().equals(Meta.NULL))
+				id = ad.id();
+
+			Object o = properties.get(id);
+
+			if (o == null) {
+				if (ad != null) {
+					if (ad.required())
+						throw new IllegalStateException("Attribute is required but not set "
+								+ method.getName());
+
+					o = ad.deflt();
+					if (o.equals(Meta.NULL))
+						o = null;
+				}
+			}
+			if (o == null) {
+				if (method.getReturnType().isPrimitive()
+						|| Number.class.isAssignableFrom(method.getReturnType())) {
+
+					o = "0";
+				} else
+					return null;
+			}
+
+			return convert(method.getGenericReturnType(), o);
+		}
+
+		@SuppressWarnings( { "unchecked" }) public Object convert(Type type, Object o)
+				throws Exception {
+			if (type instanceof ParameterizedType) {
+				ParameterizedType pType = (ParameterizedType) type;
+				return convert(pType, o);
+			}
+
+			if (type instanceof GenericArrayType) {
+				GenericArrayType gType = (GenericArrayType) type;
+				return convertArray(gType.getGenericComponentType(), o);
+			}
+
+			Class<?> resultType = (Class<?>) type;
+
+			if (resultType.isArray()) {
+				return convertArray(resultType.getComponentType(), o);
+			}
+
+			Class<?> actualType = o.getClass();
+			if (actualType.isAssignableFrom(resultType))
+				return o;
+
+			if (resultType == boolean.class || resultType == Boolean.class) {
+				if ( actualType == boolean.class || actualType == Boolean.class)
+					return o;
+				
+				if (Number.class.isAssignableFrom(actualType)) {
+					double b = ((Number) o).doubleValue();
+					if (b == 0)
+						return false;
+					else
+						return true;
+				}
+				return true;
+				
+			} else if (resultType == byte.class || resultType == Byte.class) {
+				if (Number.class.isAssignableFrom(actualType))
+					return ((Number) o).byteValue();
+				resultType = Byte.class;
+			} else if (resultType == char.class) {
+				resultType = Character.class;
+			} else if (resultType == short.class) {
+				if (Number.class.isAssignableFrom(actualType))
+					return ((Number) o).shortValue();
+				resultType = Short.class;
+			} else if (resultType == int.class) {
+				if (Number.class.isAssignableFrom(actualType))
+					return ((Number) o).intValue();
+				resultType = Integer.class;
+			} else if (resultType == long.class) {
+				if (Number.class.isAssignableFrom(actualType))
+					return ((Number) o).longValue();
+				resultType = Long.class;
+			} else if (resultType == float.class) {
+				if (Number.class.isAssignableFrom(actualType))
+					return ((Number) o).floatValue();
+				resultType = Float.class;
+			} else if (resultType == double.class) {
+				if (Number.class.isAssignableFrom(actualType))
+					return ((Number) o).doubleValue();
+				resultType = Double.class;
+			}
+
+			if (resultType.isPrimitive())
+				throw new IllegalArgumentException("Unknown primitive: " + resultType);
+
+			if (Number.class.isAssignableFrom(resultType) && actualType == Boolean.class) {
+				Boolean b = (Boolean) o;
+				o = b ? "1" : "0";
+			} else if (actualType == String.class) {
+				String input = (String) o;
+				if (Enum.class.isAssignableFrom(resultType)) {
+					return Enum.valueOf((Class<Enum>) resultType, input);
+				}
+				if (resultType == Class.class && loader != null) {
+					return loader.loadClass(input);
+				}
+				if (resultType == Pattern.class) {
+					return Pattern.compile(input);
+				}
+			}
+
+			try {
+				Constructor<?> c = resultType.getConstructor(String.class);
+				return c.newInstance(o.toString());
+			} catch (Throwable t) {
+				// handled on next line
+			}
+			throw new IllegalArgumentException("No conversion to " + resultType + " from "
+					+ actualType + " value " + o);
+		}
+
+		private Object convert(ParameterizedType pType, Object o) throws InstantiationException,
+				IllegalAccessException, Exception {
+			Class<?> resultType = (Class<?>) pType.getRawType();
+			if (Collection.class.isAssignableFrom(resultType)) {
+				Collection<?> input = toCollection(o);
+				if (resultType.isInterface()) {
+					if (resultType == Collection.class || resultType == List.class)
+						resultType = ArrayList.class;
+					else if (resultType == Set.class || resultType == SortedSet.class)
+						resultType = TreeSet.class;
+					else if (resultType == Queue.class /*|| resultType == Deque.class*/)
+						resultType = LinkedList.class;
+					else if (resultType == Queue.class /*|| resultType == Deque.class*/)
+						resultType = LinkedList.class;
+					else
+						throw new IllegalArgumentException(
+								"Unknown interface for a collection, no concrete class found: "
+										+ resultType);
+				}
+				
+				@SuppressWarnings("unchecked") Collection<Object> result = (Collection<Object>) resultType
+						.newInstance();
+				Type componentType = pType.getActualTypeArguments()[0];
+
+				for (Object i : input) {
+					result.add(convert(componentType, i));
+				}
+				return result;
+			} else if (pType.getRawType() == Class.class) {
+				return loader.loadClass(o.toString());
+			}
+			throw new IllegalArgumentException("cannot convert to " + pType
+					+ " because it uses generics and is not a Collection");
+		}
+
+		Object convertArray(Type componentType, Object o) throws Exception {
+			Collection<?> input = toCollection(o);
+			Class<?> componentClass = getRawClass(componentType);
+			Object array = Array.newInstance(componentClass, input.size());
+
+			int i = 0;
+			for (Object next : input) {
+				Array.set(array, i++, convert(componentType, next));
+			}
+			return array;
+		}
+
+		private Class<?> getRawClass(Type type) {
+			if (type instanceof Class)
+				return (Class<?>) type;
+
+			if (type instanceof ParameterizedType)
+				return (Class<?>) ((ParameterizedType) type).getRawType();
+
+			throw new IllegalArgumentException(
+					"For the raw type, type must be ParamaterizedType or Class but is " + type);
+		}
+
+		private Collection<?> toCollection(Object o) {
+			if (o instanceof Collection)
+				return (Collection<?>) o;
+
+			if (o.getClass().isArray()) {
+				if ( o.getClass().getComponentType().isPrimitive()) {
+					int length = Array.getLength(o);
+					List<Object> result = new ArrayList<Object>(length);
+					for ( int i=0; i<length; i++) {
+						result.add( Array.get(o, i));
+					}
+					return result;
+				} else
+					return Arrays.asList((Object[]) o);
+			}
+
+			if ( o instanceof String) {
+				String s = (String)o;
+				if (s.indexOf('|')>0)
+					return Arrays.asList(s.split("\\|"));					
+			}
+			return Arrays.asList(o);
+		}
+
+	}
+	
+	
+	public static String mangleMethodName(String id) {
+		StringBuilder sb = new StringBuilder(id);
+		for ( int i =0; i<sb.length(); i++) {
+			char c  = sb.charAt(i);
+			boolean twice = i < sb.length()-1 && sb.charAt(i+1) ==c;
+			if ( c == '$' || c == '_') {
+				if ( twice )
+					sb.deleteCharAt(i+1);
+				else 
+					if ( c == '$')
+						sb.deleteCharAt(i--); // Remove dollars
+					else
+						sb.setCharAt(i, '.'); // Make _ into .
+			}				
+		}
+		return sb.toString();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/Meta.java b/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/Meta.java
new file mode 100644
index 0000000..a2686e5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/Meta.java
@@ -0,0 +1,180 @@
+package aQute.bnd.annotation.metatype;
+
+import java.lang.annotation.*;
+
+/**
+ * The Metadata interface provides access to the properties that underly a
+ * Configurable interface. Any Configurable interface can implement this
+ * interface. The interface provides the annotations that can be used to create
+ * metatype objects.
+ * 
+ * @ConsumerInterface
+ */
+
+public interface Meta {
+	enum Type {
+		Boolean,
+		Byte,
+		Character,
+		Short,
+		Integer,
+		Long,
+		Float,
+		Double,
+		String,
+		Password
+	}
+
+	/**
+	 * Constant NULL for default usage
+	 */
+	final String	NULL	= "§NULL§";
+
+	/**
+	 * The OCD Annotation maps to the OCD element in the Metatype specification.
+	 * The only difference is that it is possible to create a Designate element
+	 * as well.
+	 * 
+	 */
+	@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface OCD {
+		/**
+		 * The name for this component. The default name is a the short class
+		 * name that us un-camel cased to make it more readable.
+		 * 
+		 * @return The name of this component
+		 */
+		String name() default NULL;
+
+		/**
+		 * The id of the component. Default the name of the class in FQN
+		 * notation but with nested classes using the $ as separator (not .).
+		 * 
+		 * The Felix webconsole always uses this id as the PID and not the pid
+		 * in the Designate element. Reported as an error.
+		 * 
+		 * @return the id
+		 */
+		String id() default NULL;
+
+		/**
+		 * The localization prefix. The default localization prefix is the name
+		 * of the class with a $ separator for nested classes.
+		 * 
+		 * @return the localization prefix.
+		 */
+		String localization() default NULL;
+
+		/**
+		 * A description for this ocd. The default is empty.
+		 * 
+		 * @return the description
+		 */
+		String description() default NULL;
+
+		/**
+		 * Defines if this is for a factory or not.
+		 */
+		boolean factory() default false;
+	}
+
+	/**
+	 * The AD element in the Metatype specification.
+	 * 
+	 */
+	@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface AD {
+		/**
+		 * A description of the attribute. Default is empty.
+		 * 
+		 * @return The description of the attribute.
+		 */
+		String description() default NULL;
+
+		/**
+		 * The name of the attribute. By default the un-camel cased version of
+		 * the method name.
+		 * 
+		 * @return the name
+		 */
+		String name() default NULL;
+
+		/**
+		 * The id of the attribute. By default the name of the method. The id is
+		 * the key used to access the properties. This is the reason the AD is a
+		 * runtime annotation so the runtime can find the proper key.
+		 * 
+		 * @return the id
+		 */
+		String id() default NULL;
+
+		/**
+		 * The type of the field. This must be one of the basic types in the
+		 * metatype specification. By default, the type is derived from the
+		 * return type of the method. This includes most collections and arrays.
+		 * Unrecognized types are defaulted to String.
+		 * 
+		 * @return the type to be used.
+		 */
+		Type type() default Type.String;
+
+		/**
+		 * The cardinality of the attribute. If not explicitly set it will be
+		 * derived from the attributes return type. Collections return
+		 * Integer.MIN_VALUE and arrays use Integer.MAX_VALUE.
+		 * 
+		 * If a single string needs to be converted to a Collection or array
+		 * then the | will be used as a separator to split the line.
+		 * 
+		 * @return the cardinality of the attribute
+		 */
+		int cardinality() default 0;
+
+		/**
+		 * The minimum value. This string must be converted to the attribute
+		 * type before comparison takes place.
+		 * 
+		 * @return the min value
+		 */
+		String min() default NULL;
+
+		/**
+		 * The maximum value. This string must be converted to the attribute
+		 * type before comparison takes place.
+		 * 
+		 * @return the max value
+		 */
+		String max() default NULL;
+
+		/**
+		 * The default value. This value must be converted to the return type of
+		 * the attribute. For multi valued returns use the | as separator.
+		 * 
+		 * @return the default value
+		 */
+		String deflt() default NULL;
+
+		/**
+		 * Indicates that this attribute is required. By default attributes are
+		 * required.
+		 * 
+		 * @return
+		 */
+		boolean required() default true;
+
+		/**
+		 * Provide labels for options. These labels must match the values. If no
+		 * labels are set, the un-cameled version of the values are used (if
+		 * they are set of course).
+		 * 
+		 * @return the option labels
+		 */
+		String[] optionLabels() default NULL;
+
+		/**
+		 * The values of options. If not set and the return type is an enum
+		 * class then the values will be derived from this return type.
+		 * 
+		 * @return the option labels
+		 */
+		String[] optionValues() default NULL;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/packageinfo b/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/packageinfo
new file mode 100644
index 0000000..ec0efd4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/packageinfo
@@ -0,0 +1 @@
+version 1.43.1
diff --git a/bundleplugin/src/main/java/aQute/bnd/annotation/packageinfo b/bundleplugin/src/main/java/aQute/bnd/annotation/packageinfo
new file mode 100644
index 0000000..ec0efd4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/packageinfo
@@ -0,0 +1 @@
+version 1.43.1
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/CircularDependencyException.java b/bundleplugin/src/main/java/aQute/bnd/build/CircularDependencyException.java
new file mode 100644
index 0000000..f08255b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/CircularDependencyException.java
@@ -0,0 +1,10 @@
+package aQute.bnd.build;
+
+public class CircularDependencyException extends Exception {
+    public CircularDependencyException(String string) {
+        super(string);
+    }
+
+    private static final long serialVersionUID = 1L;
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/Container.java b/bundleplugin/src/main/java/aQute/bnd/build/Container.java
new file mode 100644
index 0000000..72715b0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Container.java
@@ -0,0 +1,203 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.lib.osgi.*;
+
+public class Container {
+	public enum TYPE {
+		REPO, PROJECT, EXTERNAL, LIBRARY, ERROR
+	}
+
+	final File					file;
+	final TYPE					type;
+	final String				bsn;
+	final String				version;
+	final String				error;
+	final Project				project;
+	volatile Map<String, String>	attributes;
+	private long				manifestTime;
+	private Manifest			manifest;
+
+	Container(Project project, String bsn, String version, TYPE type, File source, String error,
+			Map<String, String> attributes) {
+		this.bsn = bsn;
+		this.version = version;
+		this.type = type;
+		this.file = source != null ? source : new File("/" + bsn + ":" + version + ":" + type);
+		this.project = project;
+		this.error = error;
+		if (attributes == null || attributes.isEmpty())
+			this.attributes = Collections.emptyMap();
+		else
+			this.attributes = attributes;
+	}
+
+	public Container(Project project, File file) {
+		this(project, file.getName(), "project", TYPE.PROJECT, file, null, null);
+	}
+
+	public Container(File file) {
+		this(null, file.getName(), "project", TYPE.EXTERNAL, file, null, null);
+	}
+
+	public File getFile() {
+		return file;
+	}
+
+	/**
+	 * Iterate over the containers and get the files they represent
+	 * 
+	 * @param files
+	 * @return
+	 * @throws Exception
+	 */
+	public boolean contributeFiles(List<File> files, Processor reporter) throws Exception {
+		switch (type) {
+		case EXTERNAL:
+		case REPO:
+			files.add(file);
+			return true;
+
+		case PROJECT:
+			File[] fs = project.build();
+			reporter.getInfo(project);
+			if (fs == null)
+				return false;
+
+			for (File f : fs)
+				files.add(f);
+			return true;
+
+		case LIBRARY:
+			List<Container> containers = getMembers();
+			for (Container container : containers) {
+				if (!container.contributeFiles(files, reporter))
+					return false;
+			}
+			return true;
+
+		case ERROR:
+			reporter.error(error);
+			return false;
+		}
+		return false;
+	}
+
+	public String getBundleSymbolicName() {
+		return bsn;
+	}
+
+	public String getVersion() {
+		return version;
+	}
+
+	public TYPE getType() {
+		return type;
+	}
+
+	public String getError() {
+		return error;
+	}
+
+	public boolean equals(Object other) {
+		if (other instanceof Container)
+			return file.equals(((Container) other).file);
+		else
+			return false;
+	}
+
+	public int hashCode() {
+		return file.hashCode();
+	}
+
+	public Project getProject() {
+		return project;
+	}
+
+	/**
+	 * Must show the file name or the error formatted as a file name
+	 * 
+	 * @return
+	 */
+	public String toString() {
+		if (getError() != null)
+			return "/error/" + getError();
+		else
+			return getFile().getAbsolutePath();
+	}
+
+	public Map<String, String> getAttributes() {
+		return attributes;
+	}
+	
+	public void putAttribute(String name, String value) {
+		if (attributes == Collections.<String,String>emptyMap())
+			attributes = new HashMap<String, String>(1);
+		attributes.put(name, value);
+	}
+
+	/**
+	 * Return the this if this is anything else but a library. If it is a
+	 * library, return the members. This could work recursively, e.g., libraries
+	 * can point to libraries.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	public List<Container> getMembers() throws Exception {
+		List<Container> result = project.newList();
+
+		// Are ww a library? If no, we are the result
+		if (getType() == TYPE.LIBRARY) {
+			// We are a library, parse the file. This is
+			// basically a specification clause per line.
+			// I.e. you can do bsn; version, bsn2; version. But also
+			// spread it out over lines.
+			InputStream in = new FileInputStream(file);
+			BufferedReader rd = new BufferedReader(new InputStreamReader(in,
+					Constants.DEFAULT_CHARSET));
+			try {
+				String line;
+				while ((line = rd.readLine()) != null) {
+					line = line.trim();
+					if (!line.startsWith("#") && line.length() > 0) {
+						List<Container> list = project.getBundles(Strategy.HIGHEST, line, null);
+						result.addAll(list);
+					}
+				}
+			} finally {
+				in.close();
+			}
+		} else
+			result.add(this);
+
+		return result;
+	}
+
+	/**
+	 * Answer the manifest for this container (if possible). Manifest is cached
+	 * until the file is renewed.
+	 */
+
+	public Manifest getManifest() throws Exception {
+		if (getError() != null || getFile() == null)
+			return null;
+
+		if (manifestTime < getFile().lastModified()) {
+			InputStream in = new FileInputStream(getFile());
+			try {
+				JarInputStream jin = new JarInputStream(in);
+				manifest = jin.getManifest();
+				jin.close();
+				manifestTime = getFile().lastModified();
+			} finally {
+				in.close();
+			}
+		}
+		return manifest;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/Project.java b/bundleplugin/src/main/java/aQute/bnd/build/Project.java
new file mode 100644
index 0000000..d05ba0b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Project.java
@@ -0,0 +1,1832 @@
+package aQute.bnd.build;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.lang.reflect.Constructor;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.jar.Manifest;
+
+import aQute.bnd.help.Syntax;
+import aQute.bnd.maven.support.Pom;
+import aQute.bnd.maven.support.ProjectPom;
+import aQute.bnd.service.CommandPlugin;
+import aQute.bnd.service.DependencyContributor;
+import aQute.bnd.service.Deploy;
+import aQute.bnd.service.RepositoryPlugin;
+import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.bnd.service.Scripter;
+import aQute.bnd.service.action.Action;
+import aQute.bnd.service.action.NamedAction;
+import aQute.lib.io.IO;
+import aQute.lib.osgi.Builder;
+import aQute.lib.osgi.Constants;
+import aQute.lib.osgi.Instruction;
+import aQute.lib.osgi.Jar;
+import aQute.lib.osgi.Processor;
+import aQute.lib.osgi.Resource;
+import aQute.lib.osgi.eclipse.EclipseClasspath;
+import aQute.libg.generics.Create;
+import aQute.libg.header.OSGiHeader;
+import aQute.libg.sed.Sed;
+
+/**
+ * This class is NOT threadsafe
+ * 
+ * @author aqute
+ * 
+ */
+
+public class Project extends Processor {
+
+	final static String			DEFAULT_ACTIONS			= "build; label='Build', test; label='Test', run; label='Run', clean; label='Clean', release; label='Release', refreshAll; label=Refresh, deploy;label=Deploy";
+	public final static String	BNDFILE					= "bnd.bnd";
+	public final static String	BNDCNF					= "cnf";
+	final Workspace				workspace;
+	boolean						preparedPaths;
+	final Collection<Project>	dependson				= new LinkedHashSet<Project>();
+	final Collection<Container>	buildpath				= new LinkedHashSet<Container>();
+	final Collection<Container>	testpath				= new LinkedHashSet<Container>();
+	final Collection<Container>	runpath					= new LinkedHashSet<Container>();
+	final Collection<Container>	runbundles				= new LinkedHashSet<Container>();
+	File						runstorage;
+	final Collection<File>		sourcepath				= new LinkedHashSet<File>();
+	final Collection<File>		allsourcepath			= new LinkedHashSet<File>();
+	final Collection<Container>	bootclasspath			= new LinkedHashSet<Container>();
+	final Lock					lock					= new ReentrantLock(true);
+	volatile String				lockingReason;
+	volatile Thread				lockingThread;
+	File						output;
+	File						target;
+	boolean						inPrepare;
+	int							revision;
+	File						files[];
+	private long				buildtime;
+	static List<Project>		trail					= new ArrayList<Project>();
+	boolean						delayRunDependencies	= false;
+
+	public Project(Workspace workspace, File projectDir, File buildFile) throws Exception {
+		super(workspace);
+		this.workspace = workspace;
+		setFileMustExist(false);
+		setProperties(buildFile);
+		assert workspace != null;
+		// For backward compatibility reasons, we also read
+		readBuildProperties();
+	}
+
+	public Project(Workspace workspace, File buildDir) throws Exception {
+		this(workspace, buildDir, new File(buildDir, BNDFILE));
+	}
+
+	private void readBuildProperties() throws Exception {
+		try {
+			File f = getFile("build.properties");
+			if (f.isFile()) {
+				Properties p = loadProperties(f);
+				for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();) {
+					String key = (String) e.nextElement();
+					String newkey = key;
+					if (key.indexOf('$') >= 0) {
+						newkey = getReplacer().process(key);
+					}
+					setProperty(newkey, p.getProperty(key));
+				}
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	public static Project getUnparented(File propertiesFile) throws Exception {
+		propertiesFile = propertiesFile.getAbsoluteFile();
+		Workspace workspace = new Workspace(propertiesFile.getParentFile());
+		Project project = new Project(workspace, propertiesFile.getParentFile());
+		project.setProperties(propertiesFile);
+		project.setFileMustExist(true);
+		return project;
+	}
+
+	public synchronized boolean isValid() {
+		return getBase().isDirectory() && getPropertiesFile().isFile();
+	}
+
+	/**
+	 * Return a new builder that is nicely setup for this project. Please close
+	 * this builder after use.
+	 * 
+	 * @param parent
+	 *            The project builder to use as parent, use this project if null
+	 * @return
+	 * @throws Exception
+	 */
+	public synchronized ProjectBuilder getBuilder(ProjectBuilder parent) throws Exception {
+
+		ProjectBuilder builder;
+
+		if (parent == null)
+			builder = new ProjectBuilder(this);
+		else
+			builder = new ProjectBuilder(parent);
+
+		builder.setBase(getBase());
+
+		return builder;
+	}
+
+	public synchronized int getChanged() {
+		return revision;
+	}
+
+	/*
+	 * Indicate a change in the external world that affects our build. This will
+	 * clear any cached results.
+	 */
+	public synchronized void setChanged() {
+		// if (refresh()) {
+		preparedPaths = false;
+		files = null;
+		revision++;
+		// }
+	}
+
+	public Workspace getWorkspace() {
+		return workspace;
+	}
+
+	public String toString() {
+		return getBase().getName();
+	}
+
+	/**
+	 * Set up all the paths
+	 */
+
+	public synchronized void prepare() throws Exception {
+		if (!isValid()) {
+			warning("Invalid project attempts to prepare: %s", this);
+			return;
+		}
+
+		if (inPrepare)
+			throw new CircularDependencyException(trail.toString() + "," + this);
+
+		trail.add(this);
+		try {
+			if (!preparedPaths) {
+				inPrepare = true;
+				try {
+					dependson.clear();
+					buildpath.clear();
+					sourcepath.clear();
+					allsourcepath.clear();
+					bootclasspath.clear();
+					testpath.clear();
+					runpath.clear();
+					runbundles.clear();
+
+					// We use a builder to construct all the properties for
+					// use.
+					setProperty("basedir", getBase().getAbsolutePath());
+
+					// If a bnd.bnd file exists, we read it.
+					// Otherwise, we just do the build properties.
+					if (!getPropertiesFile().isFile() && new File(getBase(), ".classpath").isFile()) {
+						// Get our Eclipse info, we might depend on other
+						// projects
+						// though ideally this should become empty and void
+						doEclipseClasspath();
+					}
+
+					// Calculate our source directory
+
+					File src = getSrc();
+					if (src.isDirectory()) {
+						sourcepath.add(src);
+						allsourcepath.add(src);
+					} else
+						sourcepath.add(getBase());
+
+					// Set default bin directory
+					output = getFile(getProperty("bin", "bin")).getAbsoluteFile();
+					if (!output.exists()) {
+						output.mkdirs();
+						getWorkspace().changedFile(output);
+					}
+					if (!output.isDirectory())
+						error("Can not find output directory: " + output);
+					else if (!buildpath.contains(output))
+						buildpath.add(new Container(this, output));
+
+					// Where we store all our generated stuff.
+					target = getFile(getProperty("target", "generated"));
+					if (!target.exists()) {
+						target.mkdirs();
+						getWorkspace().changedFile(target);
+					}
+					
+					// Where the launched OSGi framework stores stuff
+					String runStorageStr = getProperty(Constants.RUNSTORAGE);
+					runstorage = runStorageStr != null ? getFile(runStorageStr) : null;
+
+					// We might have some other projects we want build
+					// before we do anything, but these projects are not in
+					// our path. The -dependson allows you to build them before.
+
+					List<Project> dependencies = new ArrayList<Project>();
+					// dependencies.add( getWorkspace().getProject("cnf"));
+
+					String dp = getProperty(Constants.DEPENDSON);
+					Set<String> requiredProjectNames = parseHeader(dp).keySet();
+					List<DependencyContributor> dcs = getPlugins(DependencyContributor.class);
+					for (DependencyContributor dc : dcs)
+						dc.addDependencies(this, requiredProjectNames);
+
+					for (String p : requiredProjectNames) {
+						Project required = getWorkspace().getProject(p);
+						if (required == null)
+							error("No such project " + required + " on " + Constants.DEPENDSON);
+						else {
+							dependencies.add(required);
+						}
+
+					}
+
+					// We have two paths that consists of repo files, projects,
+					// or some other stuff. The doPath routine adds them to the
+					// path and extracts the projects so we can build them
+					// before.
+
+					doPath(buildpath, dependencies, parseBuildpath(), bootclasspath);
+					doPath(testpath, dependencies, parseTestpath(), bootclasspath);
+					if (!delayRunDependencies) {
+						doPath(runpath, dependencies, parseRunpath(), null);
+						doPath(runbundles, dependencies, parseRunbundles(), null);
+					}
+
+					// We now know all dependent projects. But we also depend
+					// on whatever those projects depend on. This creates an
+					// ordered list without any duplicates. This of course
+					// assumes
+					// that there is no circularity. However, this is checked
+					// by the inPrepare flag, will throw an exception if we
+					// are circular.
+
+					Set<Project> done = new HashSet<Project>();
+					done.add(this);
+					allsourcepath.addAll(sourcepath);
+
+					for (Project project : dependencies)
+						project.traverse(dependson, done);
+
+					for (Project project : dependson) {
+						allsourcepath.addAll(project.getSourcePath());
+					}
+					if (isOk())
+						preparedPaths = true;
+				} finally {
+					inPrepare = false;
+				}
+			}
+		} finally {
+			trail.remove(this);
+		}
+	}
+
+	public File getSrc() {
+		return new File(getBase(), getProperty("src", "src"));
+	}
+
+	private void traverse(Collection<Project> dependencies, Set<Project> visited) throws Exception {
+		if (visited.contains(this))
+			return;
+
+		visited.add(this);
+
+		for (Project project : getDependson())
+			project.traverse(dependencies, visited);
+
+		dependencies.add(this);
+	}
+
+	/**
+	 * Iterate over the entries and place the projects on the projects list and
+	 * all the files of the entries on the resultpath.
+	 * 
+	 * @param resultpath
+	 *            The list that gets all the files
+	 * @param projects
+	 *            The list that gets any projects that are entries
+	 * @param entries
+	 *            The input list of classpath entries
+	 */
+	private void doPath(Collection<Container> resultpath, Collection<Project> projects,
+			Collection<Container> entries, Collection<Container> bootclasspath) {
+		for (Container cpe : entries) {
+			if (cpe.getError() != null)
+				error(cpe.getError());
+			else {
+				if (cpe.getType() == Container.TYPE.PROJECT) {
+					projects.add(cpe.getProject());
+				}
+				if (bootclasspath != null && cpe.getBundleSymbolicName().startsWith("ee.")
+						|| cpe.getAttributes().containsKey("boot"))
+					bootclasspath.add(cpe);
+				else
+					resultpath.add(cpe);
+			}
+		}
+	}
+
+	/**
+	 * Parse the list of bundles that are a prerequisite to this project.
+	 * 
+	 * Bundles are listed in repo specific names. So we just let our repo
+	 * plugins iterate over the list of bundles and we get the highest version
+	 * from them.
+	 * 
+	 * @return
+	 */
+
+	private List<Container> parseBuildpath() throws Exception {
+		List<Container> bundles = getBundles(Strategy.LOWEST, getProperty(Constants.BUILDPATH), Constants.BUILDPATH);
+		appendPackages(Strategy.LOWEST, getProperty(Constants.BUILDPACKAGES), bundles, ResolverMode.build);
+		return bundles;
+	}
+
+	private List<Container> parseRunpath() throws Exception {
+		return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNPATH), Constants.RUNPATH);
+	}
+
+	private List<Container> parseRunbundles() throws Exception {
+		return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNBUNDLES), Constants.RUNBUNDLES);
+	}
+	
+	private List<Container> parseTestpath() throws Exception {
+		return getBundles(Strategy.HIGHEST, getProperty(Constants.TESTPATH), Constants.TESTPATH);
+	}
+	
+	/**
+	 * Analyze the header and return a list of files that should be on the
+	 * build, test or some other path. The list is assumed to be a list of bsns
+	 * with a version specification. The special case of version=project
+	 * indicates there is a project in the same workspace. The path to the
+	 * output directory is calculated. The default directory ${bin} can be
+	 * overridden with the output attribute.
+	 * 
+	 * @param strategy
+	 *            STRATEGY_LOWEST or STRATEGY_HIGHEST
+	 * @param spec
+	 *            The header
+	 * @return
+	 */
+	public List<Container> getBundles(Strategy strategyx, String spec, String source) throws Exception {
+		List<Container> result = new ArrayList<Container>();
+		Map<String, Map<String, String>> bundles = parseHeader(spec);
+
+		try {
+			for (Iterator<Map.Entry<String, Map<String, String>>> i = bundles.entrySet().iterator(); i
+					.hasNext();) {
+				Map.Entry<String, Map<String, String>> entry = i.next();
+				String bsn = entry.getKey();
+				Map<String, String> attrs = entry.getValue();
+
+				Container found = null;
+
+				// Check if we have to use the maven pom ...
+				if (bsn.equals("pom")) {
+					doMavenPom(strategyx, result, attrs.get("scope"));
+					continue;
+				}
+
+				String versionRange = attrs.get("version");
+
+				if (versionRange != null) {
+					if (versionRange.equals("latest") || versionRange.equals("snapshot")) {
+						found = getBundle(bsn, versionRange, strategyx, attrs);
+					}
+				}
+				if (found == null) {
+					if (versionRange != null
+							&& (versionRange.equals("project") || versionRange.equals("latest"))) {
+						Project project = getWorkspace().getProject(bsn);
+						if (project != null && project.exists()) {
+							File f = project.getOutput();
+							found = new Container(project, bsn, "project", Container.TYPE.PROJECT,
+									f, null, attrs);
+						} else {
+							error("Reference to project that does not exist in workspace\n"
+									+ "  Project       %s\n" + "  Specification %s", bsn, spec);
+							continue;
+						}
+					} else if (versionRange != null && versionRange.equals("file")) {
+						File f = getFile(bsn);
+						String error = null;
+						if (!f.exists())
+							error = "File does not exist: " + f.getAbsolutePath();
+						if (f.getName().endsWith(".lib")) {
+							found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f,
+									error, attrs);
+						} else {
+							found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f,
+									error, attrs);
+						}
+					} else {
+						found = getBundle(bsn, versionRange, strategyx, attrs);
+					}
+				}
+
+				if (found != null) {
+					List<Container> libs = found.getMembers();
+					for (Container cc : libs) {
+						if (result.contains(cc))
+							warning("Multiple bundles with the same final URL: " + cc);
+
+						result.add(cc);
+					}
+				} else {
+					// Oops, not a bundle in sight :-(
+					Container x = new Container(this, bsn, versionRange, Container.TYPE.ERROR,
+							null, bsn + ";version=" + versionRange + " not found", attrs);
+					result.add(x);
+					warning("Can not find URL for bsn " + bsn);
+				}
+			}
+		} catch (CircularDependencyException e) {
+			String message = e.getMessage();
+			if (source != null)
+				message = String.format("%s (from property: %s)", message, source);
+			error("Circular dependency detected from project %s: %s", e, getName(), message);
+		} catch (Exception e) {
+			error("Unexpected error while trying to get the bundles from " + spec, e);
+			e.printStackTrace();
+		}
+		return result;
+	}
+	
+	/**
+	 * Calculates the containers required to fulfil the {@code -buildpackages}
+	 * instruction, and appends them to the existing list of containers.
+	 * 
+	 * @param strategyx
+	 *            The package-version disambiguation strategy.
+	 * @param spec
+	 *            The value of the @{code -buildpackages} instruction.
+	 * @throws Exception
+	 */
+	public void appendPackages(Strategy strategyx, String spec, List<Container> resolvedBundles, ResolverMode mode) throws Exception {
+		Map<File, Container> pkgResolvedBundles = new HashMap<File, Container>();
+		
+		List<Entry<String, Map<String, String>>> queue = new LinkedList<Map.Entry<String,Map<String,String>>>();
+		queue.addAll(parseHeader(spec).entrySet());
+		
+		while (!queue.isEmpty()) {
+			Entry<String, Map<String, String>> entry = queue.remove(0);
+			
+			String pkgName = entry.getKey();
+			Map<String, String> attrs = entry.getValue();
+			
+			Container found = null;
+			
+			String versionRange = attrs.get(Constants.VERSION_ATTRIBUTE);
+			if ("latest".equals(versionRange) || "snapshot".equals(versionRange))
+				found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
+
+			if (found == null)
+				found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
+
+			if (found != null) {
+				if (resolvedBundles.contains(found)) {
+					// Don't add his bundle because it was already included using -buildpath
+				} else {
+					List<Container> libs = found.getMembers();
+					for (Container cc : libs) {
+						Container existing = pkgResolvedBundles.get(cc.file);
+						if (existing != null)
+							addToPackageList(existing, attrs.get("packages"));
+						else {
+							addToPackageList(cc, attrs.get("packages"));
+							pkgResolvedBundles.put(cc.file, cc);
+						}
+						
+						String importUses = cc.getAttributes().get("import-uses");
+						if (importUses != null)
+							queue.addAll(0, parseHeader(importUses).entrySet());
+					}
+				}
+			} else {
+				// Unable to resolve
+				Container x = new Container(this, "X", versionRange, Container.TYPE.ERROR, null, "package " + pkgName + ";version=" + versionRange + " not found", attrs);
+				resolvedBundles.add(x);
+				warning("Can not find URL for package " + pkgName);
+			}
+		}
+		
+		for (Container container : pkgResolvedBundles.values()) {
+			resolvedBundles.add(container);
+		}
+	}
+	
+	static void mergeNames(String names, Set<String> set) {
+		StringTokenizer tokenizer = new StringTokenizer(names, ",");
+		while (tokenizer.hasMoreTokens())
+			set.add(tokenizer.nextToken().trim());
+	}
+	
+	static String flatten(Set<String> names) {
+		StringBuilder builder = new StringBuilder();
+		boolean first = true;
+		for (String name : names) {
+			if (!first) builder.append(',');
+			builder.append(name);
+			first = false;
+		}
+		return builder.toString();
+	}
+
+	static void addToPackageList(Container container, String newPackageNames) {
+		Set<String> merged = new HashSet<String>();
+		
+		String packageListStr = container.attributes.get("packages");
+		if (packageListStr != null)
+			mergeNames(packageListStr, merged);
+		if (newPackageNames != null)
+			mergeNames(newPackageNames, merged);
+		
+		container.putAttribute("packages", flatten(merged));
+	}
+
+	/**
+	 * Find a container to fulfil a package requirement
+	 * 
+	 * @param packageName
+	 *            The package required
+	 * @param range
+	 *            The package version range required
+	 * @param strategyx
+	 *            The package-version disambiguation strategy
+	 * @param attrs
+	 *            Other attributes specified by the search.
+	 * @return
+	 * @throws Exception
+	 */
+	public Container getPackage(String packageName, String range, Strategy strategyx, Map<String, String> attrs, ResolverMode mode) throws Exception {
+		if ("snapshot".equals(range))
+			return new Container(this, "", range, Container.TYPE.ERROR, null, "snapshot not supported for package lookups", null);
+		
+		if (attrs == null)
+			attrs = new HashMap<String, String>(2);
+		attrs.put("package", packageName);
+		attrs.put("mode", mode.name());
+		
+		Strategy useStrategy = findStrategy(attrs, strategyx, range);
+		
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		for (RepositoryPlugin plugin : plugins) {
+			try {
+				File result = plugin.get(null, range, useStrategy, attrs);
+				if (result != null) {
+					if (result.getName().endsWith("lib"))
+						return new Container(this, result.getName(), range, Container.TYPE.LIBRARY, result, null, attrs);
+					else
+						return new Container(this, result.getName(), range, Container.TYPE.REPO, result, null, attrs);
+				}
+			} catch (Exception e) {
+				// Ignore... lots of repos will fail here
+			}
+		}
+		
+		return new Container(this, "X", range, Container.TYPE.ERROR, null, "package " + packageName + ";version=" + range + " Not found in " + plugins, null);
+	}
+
+	private Strategy findStrategy(Map<String, String> attrs, Strategy defaultStrategy, String versionRange) {
+		Strategy useStrategy = defaultStrategy;
+		String overrideStrategy = attrs.get("strategy");
+		if (overrideStrategy != null) {
+			if ("highest".equalsIgnoreCase(overrideStrategy))
+				useStrategy = Strategy.HIGHEST;
+			else if ("lowest".equalsIgnoreCase(overrideStrategy))
+				useStrategy = Strategy.LOWEST;
+			else if ("exact".equalsIgnoreCase(overrideStrategy))
+				useStrategy = Strategy.EXACT;
+		}
+		if ("latest".equals(versionRange))
+			useStrategy = Strategy.HIGHEST;
+		return useStrategy;
+	}
+
+	/**
+	 * The user selected pom in a path. This will place the pom as well as its
+	 * dependencies on the list
+	 * 
+	 * @param strategyx
+	 *            the strategy to use.
+	 * @param result
+	 *            The list of result containers
+	 * @param attrs
+	 *            The attributes
+	 * @throws Exception
+	 *             anything goes wrong
+	 */
+	public void doMavenPom(Strategy strategyx, List<Container> result, String action)
+			throws Exception {
+		File pomFile = getFile("pom.xml");
+		if (!pomFile.isFile())
+			error("Specified to use pom.xml but the project directory does not contain a pom.xml file");
+		else {
+			ProjectPom pom = getWorkspace().getMaven().createProjectModel(pomFile);
+			if (action == null)
+				action = "compile";
+			Pom.Scope act = Pom.Scope.valueOf(action);
+			Set<Pom> dependencies = pom.getDependencies(act);
+			for (Pom sub : dependencies) {
+				File artifact = sub.getArtifact();
+				Container container = new Container(artifact);
+				result.add(container);
+			}
+		}
+	}
+
+	public Collection<Project> getDependson() throws Exception {
+		prepare();
+		return dependson;
+	}
+
+	public Collection<Container> getBuildpath() throws Exception {
+		prepare();
+		return buildpath;
+	}
+
+	public Collection<Container> getTestpath() throws Exception {
+		prepare();
+		return testpath;
+	}
+
+	/**
+	 * Handle dependencies for paths that are calculated on demand.
+	 * 
+	 * @param testpath2
+	 * @param parseTestpath
+	 */
+	private void justInTime(Collection<Container> path, List<Container> entries) {
+		if (delayRunDependencies && path.isEmpty())
+			doPath(path, dependson, entries, null);
+	}
+
+	public Collection<Container> getRunpath() throws Exception {
+		prepare();
+		justInTime(runpath, parseRunpath());
+		return runpath;
+	}
+
+	public Collection<Container> getRunbundles() throws Exception {
+		prepare();
+		justInTime(runbundles, parseRunbundles());
+		return runbundles;
+	}
+	
+	public File getRunStorage() throws Exception {
+		prepare();
+		return runstorage;
+	}
+	
+	public boolean getRunBuilds() {
+		boolean result;
+		String runBuildsStr = getProperty(Constants.RUNBUILDS);
+		if (runBuildsStr == null)
+			result = !getPropertiesFile().getName().toLowerCase().endsWith(Constants.DEFAULT_BNDRUN_EXTENSION);
+		else
+			result = Boolean.parseBoolean(runBuildsStr);
+		return result;
+	}
+
+	public Collection<File> getSourcePath() throws Exception {
+		prepare();
+		return sourcepath;
+	}
+
+	public Collection<File> getAllsourcepath() throws Exception {
+		prepare();
+		return allsourcepath;
+	}
+
+	public Collection<Container> getBootclasspath() throws Exception {
+		prepare();
+		return bootclasspath;
+	}
+
+	public File getOutput() throws Exception {
+		prepare();
+		return output;
+	}
+
+	private void doEclipseClasspath() throws Exception {
+		EclipseClasspath eclipse = new EclipseClasspath(this, getWorkspace().getBase(), getBase());
+		eclipse.setRecurse(false);
+
+		// We get the file directories but in this case we need
+		// to tell ant that the project names
+		for (File dependent : eclipse.getDependents()) {
+			Project required = workspace.getProject(dependent.getName());
+			dependson.add(required);
+		}
+		for (File f : eclipse.getClasspath()) {
+			buildpath.add(new Container(f));
+		}
+		for (File f : eclipse.getBootclasspath()) {
+			bootclasspath.add(new Container(f));
+		}
+		sourcepath.addAll(eclipse.getSourcepath());
+		allsourcepath.addAll(eclipse.getAllSources());
+		output = eclipse.getOutput();
+	}
+
+	public String _p_dependson(String args[]) throws Exception {
+		return list(args, toFiles(getDependson()));
+	}
+
+	private Collection<?> toFiles(Collection<Project> projects) {
+		List<File> files = new ArrayList<File>();
+		for (Project p : projects) {
+			files.add(p.getBase());
+		}
+		return files;
+	}
+
+	public String _p_buildpath(String args[]) throws Exception {
+		return list(args, getBuildpath());
+	}
+
+	public String _p_testpath(String args[]) throws Exception {
+		return list(args, getRunpath());
+	}
+
+	public String _p_sourcepath(String args[]) throws Exception {
+		return list(args, getSourcePath());
+	}
+
+	public String _p_allsourcepath(String args[]) throws Exception {
+		return list(args, getAllsourcepath());
+	}
+
+	public String _p_bootclasspath(String args[]) throws Exception {
+		return list(args, getBootclasspath());
+	}
+
+	public String _p_output(String args[]) throws Exception {
+		if (args.length != 1)
+			throw new IllegalArgumentException("${output} should not have arguments");
+		return getOutput().getAbsolutePath();
+	}
+
+	private String list(String[] args, Collection<?> list) {
+		if (args.length > 3)
+			throw new IllegalArgumentException("${" + args[0]
+					+ "[;<separator>]} can only take a separator as argument, has "
+					+ Arrays.toString(args));
+
+		String separator = ",";
+
+		if (args.length == 2) {
+			separator = args[1];
+		}
+
+		return join(list, separator);
+	}
+
+	protected Object[] getMacroDomains() {
+		return new Object[] { workspace };
+	}
+
+	public File release(Jar jar) throws Exception {
+		String name = getProperty(Constants.RELEASEREPO);
+		return release(name, jar);
+	}
+
+	/**
+	 * Release
+	 * 
+	 * @param name
+	 *            The repository name
+	 * @param jar
+	 * @return
+	 * @throws Exception
+	 */
+	public File release(String name, Jar jar) throws Exception {
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		RepositoryPlugin rp = null;
+		for (RepositoryPlugin plugin : plugins) {
+			if (!plugin.canWrite()) {
+				continue;
+			}
+			if (name == null) {
+				rp = plugin;
+				break;
+			} else if (name.equals(plugin.getName())) {
+				rp = plugin;
+				break;
+			}
+		}
+
+		if (rp != null) {
+			try {
+				return rp.put(jar);
+			} catch (Exception e) {
+				error("Deploying " + jar.getName() + " on " + rp.getName(), e);
+			} finally {
+				jar.close();
+			}
+		}
+		return null;
+
+	}
+
+	public void release(boolean test) throws Exception {
+		String name = getProperty(Constants.RELEASEREPO);
+		release(name, test);
+	}
+
+	/**
+	 * Release
+	 * 
+	 * @param name
+	 *            The respository name
+	 * @param test
+	 *            Run testcases
+	 * @throws Exception
+	 */
+	public void release(String name, boolean test) throws Exception {
+		File[] jars = build(test);
+		// If build fails jars will be null
+		if (jars == null) {
+			return;
+		}
+		for (File jar : jars) {
+			Jar j = new Jar(jar);
+			release(name, j);
+			j.close();
+		}
+
+	}
+
+	/**
+	 * Get a bundle from one of the plugin repositories.
+	 * 
+	 * @param bsn
+	 *            The bundle symbolic name
+	 * @param range
+	 *            The version range
+	 * @param lowest
+	 *            set to LOWEST or HIGHEST
+	 * @return the file object that points to the bundle or null if not found
+	 * @throws Exception
+	 *             when something goes wrong
+	 */
+	public Container getBundle(String bsn, String range, Strategy strategyx,
+			Map<String, String> attrs) throws Exception {
+
+		if ("snapshot".equals(range)) {
+			return getBundleFromProject(bsn, attrs);
+		}
+
+		if ("latest".equals(range)) {
+			Container c = getBundleFromProject(bsn, attrs);
+			if (c != null)
+				return c;
+		}
+
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		;
+
+		Strategy useStrategy = strategyx;
+		if (attrs != null) {
+			String overrideStrategy = attrs.get("strategy");
+
+			if (overrideStrategy != null) {
+				if ("highest".equalsIgnoreCase(overrideStrategy))
+					useStrategy = Strategy.HIGHEST;
+				else if ("lowest".equalsIgnoreCase(overrideStrategy))
+					useStrategy = Strategy.LOWEST;
+				else if ("exact".equalsIgnoreCase(overrideStrategy))
+					useStrategy = Strategy.EXACT;
+			}
+		}
+
+		// If someone really wants the latest, lets give it to them.
+		// regardless of they asked for a lowest strategy
+		if (range != null && range.equals("latest"))
+			useStrategy = Strategy.HIGHEST;
+
+		// if ( bsn.indexOf('+')>0) {
+		// return getMavenContainer(bsn,range,useStrategy,attrs);
+		// }
+
+		// Maybe we want an exact match this time.
+		// In that case we limit the range to be exactly
+		// the version specified. We ignore it when a range
+		// is used instead of a version
+
+		for (RepositoryPlugin plugin : plugins) {
+			File result = plugin.get(bsn, range, useStrategy, attrs);
+			if (result != null) {
+				if (result.getName().endsWith("lib"))
+					return new Container(this, bsn, range, Container.TYPE.LIBRARY, result, null,
+							attrs);
+				else
+					return new Container(this, bsn, range, Container.TYPE.REPO, result, null, attrs);
+			}
+		}
+
+		return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version="
+				+ range + " Not found in " + plugins, null);
+	}
+
+	/**
+	 * Look for the bundle in the workspace. The premise is that the bsn must
+	 * start with the project name.
+	 * 
+	 * @param bsn
+	 *            The bsn
+	 * @param attrs
+	 *            Any attributes
+	 * @return
+	 * @throws Exception
+	 */
+	private Container getBundleFromProject(String bsn, Map<String, String> attrs) throws Exception {
+		String pname = bsn;
+		while (true) {
+			Project p = getWorkspace().getProject(pname);
+			if (p != null && p.isValid()) {
+				Container c = p.getDeliverable(bsn, attrs);
+				return c;
+			}
+
+			int n = pname.lastIndexOf('.');
+			if (n <= 0)
+				return null;
+			pname = pname.substring(0, n);
+		}
+	}
+
+	/**
+	 * Deploy the file (which must be a bundle) into the repository.
+	 * 
+	 * @param name
+	 *            The repository name
+	 * @param file
+	 *            bundle
+	 */
+	public void deploy(String name, File file) throws Exception {
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		;
+		RepositoryPlugin rp = null;
+		for (RepositoryPlugin plugin : plugins) {
+			if (!plugin.canWrite()) {
+				continue;
+			}
+			if (name == null) {
+				rp = plugin;
+				break;
+			} else if (name.equals(plugin.getName())) {
+				rp = plugin;
+				break;
+			}
+		}
+
+		if (rp != null) {
+			Jar jar = new Jar(file);
+			try {
+				rp.put(jar);
+				return;
+			} catch (Exception e) {
+				error("Deploying " + file + " on " + rp.getName(), e);
+			} finally {
+				jar.close();
+			}
+			return;
+		}
+		trace("No repo found " + file);
+		throw new IllegalArgumentException("No repository found for " + file);
+	}
+
+	/**
+	 * Deploy the file (which must be a bundle) into the repository.
+	 * 
+	 * @param file
+	 *            bundle
+	 */
+	public void deploy(File file) throws Exception {
+		String name = getProperty(Constants.DEPLOYREPO);
+		deploy(name, file);
+	}
+
+	/**
+	 * Deploy the current project to a repository
+	 * 
+	 * @throws Exception
+	 */
+	public void deploy() throws Exception {
+		Map<String, Map<String, String>> deploy = parseHeader(getProperty(DEPLOY));
+		if (deploy.isEmpty()) {
+			warning("Deploying but %s is not set to any repo", DEPLOY);
+			return;
+		}
+		File[] outputs = getBuildFiles();
+		for (File output : outputs) {
+			Jar jar = new Jar(output);
+			try {
+				for (Deploy d : getPlugins(Deploy.class)) {
+					trace("Deploying %s to: %s", jar, d);
+					try {
+						if (d.deploy(this, jar))
+							trace("deployed %s successfully to %s", output, d);
+					} catch (Exception e) {
+						error("Error while deploying %s, %s", this, e);
+						e.printStackTrace();
+					}
+				}
+			} finally {
+				jar.close();
+			}
+		}
+	}
+
+	/**
+	 * Macro access to the repository
+	 * 
+	 * ${repo;<bsn>[;<version>[;<low|high>]]}
+	 */
+
+	public String _repo(String args[]) throws Exception {
+		if (args.length < 2)
+			throw new IllegalArgumentException(
+					"Too few arguments for repo, syntax=: ${repo ';'<bsn> [ ; <version> [; ('HIGHEST'|'LOWEST')]}");
+
+		String bsns = args[1];
+		String version = null;
+		Strategy strategy = Strategy.HIGHEST;
+
+		if (args.length > 2) {
+			version = args[2];
+			if (args.length == 4) {
+				if (args[3].equalsIgnoreCase("HIGHEST"))
+					strategy = Strategy.HIGHEST;
+				else if (args[3].equalsIgnoreCase("LOWEST"))
+					strategy = Strategy.LOWEST;
+				else if (args[3].equalsIgnoreCase("EXACT"))
+					strategy = Strategy.EXACT;
+				else
+					error("${repo;<bsn>;<version>;<'highest'|'lowest'|'exact'>} macro requires a strategy of 'highest' or 'lowest', and is "
+							+ args[3]);
+			}
+		}
+
+		Collection<String> parts = split(bsns);
+		List<String> paths = new ArrayList<String>();
+
+		for (String bsn : parts) {
+			Container container = getBundle(bsn, version, strategy, null);
+			add(paths, container);
+		}
+		return join(paths);
+	}
+
+	private void add(List<String> paths, Container container) throws Exception {
+		if (container.getType() == Container.TYPE.LIBRARY) {
+			List<Container> members = container.getMembers();
+			for (Container sub : members) {
+				add(paths, sub);
+			}
+		} else {
+			if (container.getError() == null)
+				paths.add(container.getFile().getAbsolutePath());
+			else {
+				paths.add("<<${repo} = " + container.getBundleSymbolicName() + "-"
+						+ container.getVersion() + " : " + container.getError() + ">>");
+
+				if (isPedantic()) {
+					warning("Could not expand repo path request: %s ", container);
+				}
+			}
+
+		}
+	}
+
+	public File getTarget() throws Exception {
+		prepare();
+		return target;
+	}
+
+	/**
+	 * This is the external method that will pre-build any dependencies if it is
+	 * out of date.
+	 * 
+	 * @param underTest
+	 * @return
+	 * @throws Exception
+	 */
+	public File[] build(boolean underTest) throws Exception {
+		if (getProperty(NOBUNDLES) != null)
+			return null;
+
+		if (getProperty("-nope") != null) {
+			warning("Please replace -nope with %s", NOBUNDLES);
+			return null;
+		}
+
+
+		if (isStale()) {
+			trace("Building " + this);
+			files = buildLocal(underTest);
+		}
+
+		return files;
+	}
+	
+	/**
+	 * Return the files
+	 */
+	
+	public File[] getFiles() {
+		return files;
+	}
+	/**
+	 * Check if this project needs building
+	 */
+	public boolean isStale() throws Exception {
+		if ( files == null)
+			return true;
+		
+		long localTime = getBuildTime();
+		if ( lastModified()>localTime)
+			return true;
+		
+		for (Project dependency : getDependson()) {
+			if (dependency.isStale())
+				return true;
+	
+			if ( dependency.getBuildTime() > localTime)
+				return true;
+			
+			if ( dependency.lastModified() > localTime)
+				return true;
+			
+		}
+		return false;
+	}
+
+	/**
+	 * This method must only be called when it is sure that the project has been
+	 * build before in the same session.
+	 * 
+	 * It is a bit yucky, but ant creates different class spaces which makes it
+	 * hard to detect we already build it.
+	 * 
+	 * @return
+	 */
+
+	public File[] getBuildFiles() throws Exception {
+		return getBuildFiles(true);
+	}
+
+	public File[] getBuildFiles(boolean buildIfAbsent) throws Exception {
+		File f = new File(getTarget(), BUILDFILES);
+		if (f.isFile()) {
+			FileReader fin = new FileReader(f);
+			BufferedReader rdr = new BufferedReader(fin);
+			try {
+				List<File> files = newList();
+				for (String s = rdr.readLine(); s != null; s = rdr.readLine()) {
+					s = s.trim();
+					File ff = new File(s);
+					if (!ff.isFile()) {
+						error("buildfile lists file but the file does not exist %s", ff);
+					} else
+						files.add(ff);
+				}
+				return files.toArray(new File[files.size()]);
+			} finally {
+				fin.close();
+			}
+		}
+		if (buildIfAbsent)
+			return buildLocal(false);
+		else
+			return null;
+	}
+
+	/**
+	 * Build without doing any dependency checking. Make sure any dependent
+	 * projects are built first.
+	 * 
+	 * @param underTest
+	 * @return
+	 * @throws Exception
+	 */
+	public File[] buildLocal(boolean underTest) throws Exception {
+		if (getProperty(NOBUNDLES) != null)
+			return null;
+
+		long buildtime = System.currentTimeMillis();
+		File bfs = new File(getTarget(), BUILDFILES);
+		bfs.delete();
+
+		files = null;
+		ProjectBuilder builder = getBuilder(null);
+		if (underTest)
+			builder.setProperty(Constants.UNDERTEST, "true");
+		Jar jars[] = builder.builds();
+		File[] files = new File[jars.length];
+
+		for (int i = 0; i < jars.length; i++) {
+			Jar jar = jars[i];
+			files[i] = saveBuild(jar);
+		}
+		getInfo(builder);
+		builder.close();
+		if (isOk()) {
+			this.files = files;
+
+			// Write out the filenames in the buildfiles file
+			// so we can get them later evenin another process
+			FileWriter fw = new FileWriter(bfs);
+			try {
+				for (File f : files) {
+					fw.append(f.getAbsolutePath());
+					fw.append("\n");
+				}
+			} finally {
+				fw.close();
+			}
+			getWorkspace().changedFile(bfs);
+			this.buildtime = buildtime;
+			return files;
+		} else
+			return null;
+	}
+
+	public File saveBuild(Jar jar) throws Exception {
+		try {
+			String bsn = jar.getName();
+			File f = getOutputFile(bsn);
+			String msg = "";
+			if (!f.exists() || f.lastModified() < jar.lastModified()) {
+				reportNewer(f.lastModified(), jar);
+				f.delete();
+				if (!f.getParentFile().isDirectory())
+					f.getParentFile().mkdirs();
+				jar.write(f);
+
+				getWorkspace().changedFile(f);
+			} else {
+				msg = "(not modified since " + new Date(f.lastModified()) + ")";
+			}
+			trace(jar.getName() + " (" + f.getName() + ") " + jar.getResources().size() + " " + msg);
+			return f;
+		} finally {
+			jar.close();
+		}
+	}
+
+	private File getOutputFile(String bsn) throws Exception {
+		return new File(getTarget(), bsn + ".jar");
+	}
+
+	private void reportNewer(long lastModified, Jar jar) {
+		if (isTrue(getProperty(Constants.REPORTNEWER))) {
+			StringBuilder sb = new StringBuilder();
+			String del = "Newer than " + new Date(lastModified);
+			for (Map.Entry<String, Resource> entry : jar.getResources().entrySet()) {
+				if (entry.getValue().lastModified() > lastModified) {
+					sb.append(del);
+					del = ", \n     ";
+					sb.append(entry.getKey());
+				}
+			}
+			if (sb.length() > 0)
+				warning(sb.toString());
+		}
+	}
+
+	/**
+	 * Refresh if we are based on stale data. This also implies our workspace.
+	 */
+	public boolean refresh() {
+		boolean changed = false;
+		if (isCnf()) {
+			changed = workspace.refresh();
+		}
+		return super.refresh() || changed;
+	}
+
+	public boolean isCnf() {
+		return getBase().getName().equals(Workspace.CNFDIR);
+	}
+
+	public void propertiesChanged() {
+		super.propertiesChanged();
+		preparedPaths = false;
+		files = null;
+
+	}
+
+	public String getName() {
+		return getBase().getName();
+	}
+
+	public Map<String, Action> getActions() {
+		Map<String, Action> all = newMap();
+		Map<String, Action> actions = newMap();
+		fillActions(all);
+		getWorkspace().fillActions(all);
+
+		for (Map.Entry<String, Action> action : all.entrySet()) {
+			String key = getReplacer().process(action.getKey());
+			if (key != null && key.trim().length() != 0)
+				actions.put(key, action.getValue());
+		}
+		return actions;
+	}
+
+	public void fillActions(Map<String, Action> all) {
+		List<NamedAction> plugins = getPlugins(NamedAction.class);
+		for (NamedAction a : plugins)
+			all.put(a.getName(), a);
+
+		Map<String, Map<String, String>> actions = parseHeader(getProperty("-actions",
+				DEFAULT_ACTIONS));
+		for (Map.Entry<String, Map<String, String>> entry : actions.entrySet()) {
+			String key = Processor.removeDuplicateMarker(entry.getKey());
+			Action action;
+
+			if (entry.getValue().get("script") != null) {
+				// TODO check for the type
+				action = new ScriptAction(entry.getValue().get("type"), entry.getValue().get(
+						"script"));
+			} else {
+				action = new ReflectAction(key);
+			}
+			String label = entry.getValue().get("label");
+			all.put(label.toLowerCase(), action);
+		}
+	}
+
+	public void release() throws Exception {
+		release(false);
+	}
+
+	/**
+	 * Release.
+	 * 
+	 * @param name
+	 *            The repository name
+	 * @throws Exception
+	 */
+	public void release(String name) throws Exception {
+		release(name, false);
+	}
+
+	public void clean() throws Exception {
+		File target = getTarget();
+		if (target.isDirectory() && target.getParentFile() != null) {
+			IO.delete(target);
+		}
+		if (getOutput().isDirectory())
+			IO.delete(getOutput());
+		getOutput().mkdirs();
+	}
+
+	public File[] build() throws Exception {
+		return build(false);
+	}
+
+	public void run() throws Exception {
+		ProjectLauncher pl = getProjectLauncher();
+		pl.setTrace(isTrace());
+		pl.launch();
+	}
+
+	public void test() throws Exception {
+		clear();
+		ProjectTester tester = getProjectTester();
+		tester.setContinuous(isTrue( getProperty(Constants.TESTCONTINUOUS)));
+		tester.prepare();
+
+		if (!isOk()) {
+			return;
+		}
+		int errors = tester.test();
+		if (errors == 0) {
+			System.out.println("No Errors");
+		} else {
+			if (errors > 0) {
+				System.out.println(errors + " Error(s)");
+
+			} else
+				System.out.println("Error " + errors);
+		}
+	}
+
+	/**
+	 * This methods attempts to turn any jar into a valid jar. If this is a
+	 * bundle with manifest, a manifest is added based on defaults. If it is a
+	 * bundle, but not r4, we try to add the r4 headers.
+	 * 
+	 * @param descriptor
+	 * @param in
+	 * @return
+	 * @throws Exception
+	 */
+	public Jar getValidJar(File f) throws Exception {
+		Jar jar = new Jar(f);
+		return getValidJar(jar, f.getAbsolutePath());
+	}
+
+	public Jar getValidJar(URL url) throws Exception {
+		InputStream in = url.openStream();
+		try {
+			Jar jar = new Jar(url.getFile().replace('/', '.'), in, System.currentTimeMillis());
+			return getValidJar(jar, url.toString());
+		} finally {
+			in.close();
+		}
+	}
+
+	public Jar getValidJar(Jar jar, String id) throws Exception {
+		Manifest manifest = jar.getManifest();
+		if (manifest == null) {
+			trace("Wrapping with all defaults");
+			Builder b = new Builder(this);
+			b.addClasspath(jar);
+			b.setProperty("Bnd-Message", "Wrapped from " + id + "because lacked manifest");
+			b.setProperty(Constants.EXPORT_PACKAGE, "*");
+			b.setProperty(Constants.IMPORT_PACKAGE, "*;resolution:=optional");
+			jar = b.build();
+		} else if (manifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION) == null) {
+			trace("Not a release 4 bundle, wrapping with manifest as source");
+			Builder b = new Builder(this);
+			b.addClasspath(jar);
+			b.setProperty(Constants.PRIVATE_PACKAGE, "*");
+			b.mergeManifest(manifest);
+			String imprts = manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
+			if (imprts == null)
+				imprts = "";
+			else
+				imprts += ",";
+			imprts += "*;resolution=optional";
+
+			b.setProperty(Constants.IMPORT_PACKAGE, imprts);
+			b.setProperty("Bnd-Message", "Wrapped from " + id + "because had incomplete manifest");
+			jar = b.build();
+		}
+		return jar;
+	}
+
+	public String _project(String args[]) {
+		return getBase().getAbsolutePath();
+	}
+
+	public void bump(String mask) throws IOException {
+		Sed sed = new Sed(getReplacer(), getPropertiesFile());
+		sed.replace("(Bundle-Version\\s*(:|=)\\s*)(([0-9]+(\\.[0-9]+(\\.[0-9]+)?)?))",
+				"$1${version;" + mask + ";$3}");
+		sed.doIt();
+		refresh();
+	}
+
+	public void bump() throws IOException {
+		bump(getProperty(BUMPPOLICY, "=+0"));
+	}
+
+	public void action(String command) throws Throwable {
+		Map<String, Action> actions = getActions();
+
+		Action a = actions.get(command);
+		if (a == null)
+			a = new ReflectAction(command);
+
+		before(this, command);
+		try {
+			a.execute(this, command);
+		} catch (Throwable t) {
+			after(this, command, t);
+			throw t;
+		}
+	}
+
+	/**
+	 * Run all before command plugins
+	 * 
+	 */
+	void before(Project p, String a) {
+		List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
+		for (CommandPlugin testPlugin : testPlugins) {
+			testPlugin.before(this, a);
+		}
+	}
+
+	/**
+	 * Run all after command plugins
+	 */
+	void after(Project p, String a, Throwable t) {
+		List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
+		for (int i = testPlugins.size() - 1; i >= 0; i--) {
+			testPlugins.get(i).after(this, a, t);
+		}
+	}
+
+	public String _findfile(String args[]) {
+		File f = getFile(args[1]);
+		List<String> files = new ArrayList<String>();
+		tree(files, f, "", Instruction.getPattern(args[2]));
+		return join(files);
+	}
+
+	void tree(List<String> list, File current, String path, Instruction instr) {
+		if (path.length() > 0)
+			path = path + "/";
+
+		String subs[] = current.list();
+		if (subs != null) {
+			for (String sub : subs) {
+				File f = new File(current, sub);
+				if (f.isFile()) {
+					if (instr.matches(sub) && !instr.isNegated())
+						list.add(path + sub);
+				} else
+					tree(list, f, path + sub, instr);
+			}
+		}
+	}
+
+	public void refreshAll() {
+		workspace.refresh();
+		refresh();
+	}
+
+	@SuppressWarnings("unchecked") public void script(String type, String script) throws Exception {
+		// TODO check tyiping
+		List<Scripter> scripters = getPlugins(Scripter.class);
+		if (scripters.isEmpty()) {
+			error("Can not execute script because there are no scripters registered: %s", script);
+			return;
+		}
+		@SuppressWarnings("rawtypes")
+		Map x = (Map) getProperties();
+		scripters.get(0).eval((Map<String, Object>) x, new StringReader(script));
+	}
+
+	public String _repos(String args[]) throws Exception {
+		List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
+		List<String> names = new ArrayList<String>();
+		for (RepositoryPlugin rp : repos)
+			names.add(rp.getName());
+		return join(names, ", ");
+	}
+
+	public String _help(String args[]) throws Exception {
+		if (args.length == 1)
+			return "Specify the option or header you want information for";
+
+		Syntax syntax = Syntax.HELP.get(args[1]);
+		if (syntax == null)
+			return "No help for " + args[1];
+
+		String what = null;
+		if (args.length > 2)
+			what = args[2];
+
+		if (what == null || what.equals("lead"))
+			return syntax.getLead();
+		if (what == null || what.equals("example"))
+			return syntax.getExample();
+		if (what == null || what.equals("pattern"))
+			return syntax.getPattern();
+		if (what == null || what.equals("values"))
+			return syntax.getValues();
+
+		return "Invalid type specified for help: lead, example, pattern, values";
+	}
+
+	/**
+	 * Returns containers for the deliverables of this project. The deliverables
+	 * is the project builder for this project if no -sub is specified.
+	 * Otherwise it contains all the sub bnd files.
+	 * 
+	 * @return A collection of containers
+	 * 
+	 * @throws Exception
+	 */
+	public Collection<Container> getDeliverables() throws Exception {
+		List<Container> result = new ArrayList<Container>();
+		Collection<? extends Builder> builders = getSubBuilders();
+
+		for (Builder builder : builders) {
+			Container c = new Container(this, builder.getBsn(), builder.getVersion(),
+					Container.TYPE.PROJECT, getOutputFile(builder.getBsn()), null, null);
+			result.add(c);
+		}
+		return result;
+
+	}
+
+	/**
+	 * Return the builder associated with the give bnd file or null. The bnd.bnd
+	 * file can contain -sub option. This option allows specifying files in the
+	 * same directory that should drive the generation of multiple deliverables.
+	 * This method figures out if the bndFile is actually one of the bnd files
+	 * of a deliverable.
+	 * 
+	 * @param bndFile
+	 *            A file pointing to a bnd file.
+	 * @return null or the builder for a sub file.
+	 * @throws Exception
+	 */
+	public Builder getSubBuilder(File bndFile) throws Exception {
+		bndFile = bndFile.getCanonicalFile();
+
+		// Verify that we are inside the project.
+		File base = getBase().getCanonicalFile();
+		if (!bndFile.getAbsolutePath().startsWith(base.getAbsolutePath()))
+			return null;
+
+		Collection<? extends Builder> builders = getSubBuilders();
+		for (Builder sub : builders) {
+			File propertiesFile = sub.getPropertiesFile();
+			if (propertiesFile != null) {
+				if (propertiesFile.getCanonicalFile().equals(bndFile)) {
+					// Found it!
+					return sub;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Answer the container associated with a given bsn.
+	 * 
+	 * @param bndFile
+	 *            A file pointing to a bnd file.
+	 * @return null or the builder for a sub file.
+	 * @throws Exception
+	 */
+	public Container getDeliverable(String bsn, Map<String, String> attrs) throws Exception {
+		Collection<? extends Builder> builders = getSubBuilders();
+		for (Builder sub : builders) {
+			if (sub.getBsn().equals(bsn))
+				return new Container(this, getOutputFile(bsn));
+		}
+		return null;
+	}
+
+	/**
+	 * Get a list of the sub builders. A bnd.bnd file can contain the -sub
+	 * option. This will generate multiple deliverables. This method returns the
+	 * builders for each sub file. If no -sub option is present, the list will
+	 * contain a builder for the bnd.bnd file.
+	 * 
+	 * @return A list of builders.
+	 * @throws Exception
+	 */
+	public Collection<? extends Builder> getSubBuilders() throws Exception {
+		return getBuilder(null).getSubBuilders();
+	}
+
+	/**
+	 * Calculate the classpath. We include our own runtime.jar which includes
+	 * the test framework and we include the first of the test frameworks
+	 * specified.
+	 * 
+	 * @throws Exception
+	 */
+	Collection<File> toFile(Collection<Container> containers) throws Exception {
+		ArrayList<File> files = new ArrayList<File>();
+		for (Container container : containers) {
+			container.contributeFiles(files, this);
+		}
+		return files;
+	}
+
+	public Collection<String> getRunVM() {
+		Map<String, Map<String, String>> hdr = parseHeader(getProperty(RUNVM));
+		return hdr.keySet();
+	}
+
+	public Map<String, String> getRunProperties() {
+		return OSGiHeader.parseProperties(getProperty(RUNPROPERTIES));
+	}
+
+	/**
+	 * Get a launcher.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	public ProjectLauncher getProjectLauncher() throws Exception {
+		return getHandler(ProjectLauncher.class, getRunpath(), LAUNCHER_PLUGIN,
+				"biz.aQute.launcher");
+	}
+
+	public ProjectTester getProjectTester() throws Exception {
+		return getHandler(ProjectTester.class, getTestpath(), TESTER_PLUGIN, "biz.aQute.junit");
+	}
+
+	private <T> T getHandler(Class<T> target, Collection<Container> containers, String header,
+			String defaultHandler) throws Exception {
+		Class<? extends T> handlerClass = target;
+
+		// Make sure we find at least one handler, but hope to find an earlier
+		// one
+		List<Container> withDefault = Create.list();
+		withDefault.addAll(containers);
+		withDefault.addAll(getBundles(Strategy.HIGHEST, defaultHandler, null));
+
+		for (Container c : withDefault) {
+			Manifest manifest = c.getManifest();
+
+			if (manifest != null) {
+				String launcher = manifest.getMainAttributes().getValue(header);
+				if (launcher != null) {
+					Class<?> clz = getClass(launcher, c.getFile());
+					if (clz != null) {
+						if (!target.isAssignableFrom(clz)) {
+							error("Found a %s class in %s but it is not compatible with: %s", clz,
+									c, target);
+						} else {
+							handlerClass = clz.asSubclass(target);
+							Constructor<? extends T> constructor = handlerClass
+									.getConstructor(Project.class);
+							return constructor.newInstance(this);
+						}
+					}
+				}
+			}
+		}
+		throw new IllegalArgumentException("Default handler for " + header + " not found in "
+				+ defaultHandler);
+	}
+
+	public synchronized boolean lock(String reason) throws InterruptedException {
+		if (!lock.tryLock(5, TimeUnit.SECONDS)) {
+			error("Could not acquire lock for %s, was locked by %s for %s", reason, lockingThread,
+					lockingReason);
+			System.out.printf("Could not acquire lock for %s, was locked by %s for %s\n", reason,
+					lockingThread, lockingReason);
+			System.out.flush();
+			return false;
+		}
+		this.lockingReason = reason;
+		this.lockingThread = Thread.currentThread();
+		return true;
+	}
+
+	public void unlock() {
+		lockingReason = null;
+		lock.unlock();
+	}
+
+	public long getBuildTime() throws Exception {
+		if (buildtime == 0) {
+
+			files = getBuildFiles();
+			if (files != null && files.length >= 1)
+				buildtime = files[0].lastModified();
+		}
+		return buildtime;
+	}
+
+	/**
+	 * Make this project delay the calculation of the run dependencies.
+	 * 
+	 * The run dependencies calculation can be done in prepare or until the
+	 * dependencies are actually needed.
+	 */
+	public void setDelayRunDependencies(boolean x) {
+		delayRunDependencies = x;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java b/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
new file mode 100644
index 0000000..d9cccda
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
@@ -0,0 +1,73 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+public class ProjectBuilder extends Builder {
+    Project project;
+    boolean initialized;
+
+    public ProjectBuilder(Project project) {
+        super(project);
+        this.project = project;
+    }
+
+    public ProjectBuilder(ProjectBuilder builder) {
+        super(builder);
+        this.project = builder.project;
+    }
+
+    @Override
+    public long lastModified() {
+        return Math.max(project.lastModified(), super.lastModified());
+    }
+
+    /**
+     * We put our project and our workspace on the macro path.
+     */
+    protected Object[] getMacroDomains() {
+        return new Object[] { project, project.getWorkspace() };
+    }
+
+    public Builder getSubBuilder() throws Exception {
+        return project.getBuilder(this);
+    }
+
+    public Project getProject() {
+        return project;
+    }
+
+    public void init() {
+        try {
+            if (!initialized) {
+                initialized = true;
+                for (Container file : project.getBuildpath()) {
+                    addClasspath(file.getFile());
+                }
+
+                for (Container file : project.getBootclasspath()) {
+                    addClasspath(file.getFile());
+                }
+
+                for (File file : project.getAllsourcepath()) {
+                    addSourcepath(file);
+                }
+
+            }
+        } catch (Exception e) {
+            error("init project builder fails", e);
+        }
+    }
+
+    public List<Jar> getClasspath() {
+        init();
+        return super.getClasspath();
+    }
+
+    @Override
+    protected void changedFile(File f) {
+        project.getWorkspace().changedFile(f);
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java b/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
new file mode 100644
index 0000000..2b78943
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
@@ -0,0 +1,327 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.generics.*;
+
+/**
+ * A Project Launcher is a base class to be extended by launchers. Launchers are
+ * JARs that launch a framework and install a number of bundles and then run the
+ * framework. A launcher jar must specify a Launcher-Class manifest header. This
+ * class is instantiated and cast to a LauncherPlugin. This plug in is then
+ * asked to provide a ProjectLauncher. This project launcher is then used by the
+ * project to run the code. Launchers must extend this class.
+ * 
+ */
+public abstract class ProjectLauncher {
+	private final Project						project;
+	private long								timeout				= 0;
+	private final Collection<String>			classpath			= new ArrayList<String>();
+	private List<String>						runbundles			= Create.list();
+	private final List<String>					runvm				= new ArrayList<String>();
+	private Map<String, String>					runproperties;
+	private Command								java;
+	private Map<String, Map<String, String>>	runsystempackages;
+	private final List<String>					activators			= Create.list();
+	private File								storageDir;
+
+	private boolean								trace;
+	private boolean								keep;
+	private int									framework;
+
+	public final static int						SERVICES			= 10111;
+	public final static int						NONE				= 20123;
+
+	// MUST BE ALIGNED WITH LAUNCHER
+	public final static int						OK					= 0;
+	public final static int						WARNING				= -1;
+	public final static int						ERROR				= -2;
+	public final static int						TIMEDOUT			= -3;
+	public final static int						UPDATE_NEEDED		= -4;
+	public final static int						CANCELED			= -5;
+	public final static int						DUPLICATE_BUNDLE	= -6;
+	public final static int						RESOLVE_ERROR		= -7;
+	public final static int						ACTIVATOR_ERROR		= -8;
+	public final static int						CUSTOM_LAUNCHER		= -128;
+
+	public final static String					EMBEDDED_ACTIVATOR	= "Embedded-Activator";
+
+	public ProjectLauncher(Project project) throws Exception {
+		this.project = project;
+
+		updateFromProject();
+	}
+
+	/**
+	 * Collect all the aspect from the project and set the local fields from
+	 * them. Should be called
+	 * 
+	 * @throws Exception
+	 */
+	protected void updateFromProject() throws Exception {
+		// pkr: could not use this because this is killing the runtests.
+		// project.refresh();
+		runbundles.clear();
+		Collection<Container> run = project.getRunbundles();
+		
+		for (Container container : run) {
+			File file = container.getFile();
+			if (file != null && (file.isFile() || file.isDirectory()))
+				runbundles.add(file.getAbsolutePath());
+		}
+
+		if (project.getRunBuilds()) {
+			File[] builds = project.build();
+			if (builds != null)
+				for (File file : builds)
+					runbundles.add(file.getAbsolutePath());
+		}
+
+		Collection<Container> runpath = project.getRunpath();
+		runsystempackages = project.parseHeader(project.getProperty(Constants.RUNSYSTEMPACKAGES));
+		framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
+		trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+		timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
+		trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+		// For backward compatibility with bndtools launcher
+		List<Container> fws = project.getBundles(Strategy.HIGHEST, project.getProperty("-runfw"), "-runfw");
+		runpath.addAll(fws);
+
+		for (Container c : runpath) {
+			addClasspath(c);
+		}
+
+		runvm.addAll(project.getRunVM());
+		runproperties = project.getRunProperties();
+
+		storageDir = project.getRunStorage();
+		if (storageDir == null) {
+			storageDir = new File(project.getTarget(), "fw");
+		}
+	}
+
+	private int getRunframework(String property) {
+		if (Constants.RUNFRAMEWORK_NONE.equalsIgnoreCase(property))
+			return NONE;
+		else if (Constants.RUNFRAMEWORK_SERVICES.equalsIgnoreCase(property))
+			return SERVICES;
+
+		return SERVICES;
+	}
+
+	public void addClasspath(Container container) throws Exception {
+		if (container.getError() != null) {
+			project.error("Cannot launch because %s has reported %s", container.getProject(),
+					container.getError());
+		} else {
+			Collection<Container> members = container.getMembers();
+			for (Container m : members) {
+				String path = m.getFile().getAbsolutePath();
+				if (!classpath.contains(path)) {
+					classpath.add(path);
+
+					Manifest manifest = m.getManifest();
+					if (manifest != null) {
+						Map<String, Map<String, String>> exports = project.parseHeader(manifest
+								.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+						for (Map.Entry<String, Map<String, String>> e : exports.entrySet()) {
+							if (!runsystempackages.containsKey(e.getKey()))
+								runsystempackages.put(e.getKey(), e.getValue());
+						}
+
+						// Allow activators on the runpath. They are called
+						// after
+						// the framework is completely initialized wit the
+						// system
+						// context.
+						String activator = manifest.getMainAttributes()
+								.getValue(EMBEDDED_ACTIVATOR);
+						if (activator != null)
+							activators.add(activator);
+					}
+				}
+			}
+		}
+	}
+
+	public void addRunBundle(String f) {
+		runbundles.add(f);
+	}
+
+	public Collection<String> getRunBundles() {
+		return runbundles;
+	}
+
+	public void addRunVM(String arg) {
+		runvm.add(arg);
+	}
+
+	public Collection<String> getRunpath() {
+		return classpath;
+	}
+
+	public Collection<String> getClasspath() {
+		return classpath;
+	}
+
+	public Collection<String> getRunVM() {
+		return runvm;
+	}
+
+	public Collection<String> getArguments() {
+		return Collections.emptySet();
+	}
+
+	public Map<String, String> getRunProperties() {
+		return runproperties;
+	}
+	
+	public File getStorageDir() {
+		return storageDir;
+	}
+
+	public abstract String getMainTypeName();
+
+	public abstract void update() throws Exception;
+
+	public int launch() throws Exception {
+		prepare();
+		java = new Command();
+		java.add(project.getProperty("java", "java"));
+		java.add("-cp");
+		java.add(Processor.join(getClasspath(), File.pathSeparator));
+		java.addAll(getRunVM());
+		java.add(getMainTypeName());
+		java.addAll(getArguments());
+		if (timeout != 0)
+			java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
+
+		int result = java.execute(System.in, System.out, System.err);
+		if (result == Integer.MIN_VALUE)
+			return TIMEDOUT;
+
+		reportResult(result);
+		return result;
+	}
+
+	protected void reportResult(int result) {
+		switch (result) {
+		case OK:
+			project.trace("Command terminated normal %s", java);
+			break;
+		case TIMEDOUT:
+			project.error("Launch timedout: %s", java);
+			break;
+
+		case ERROR:
+			project.error("Launch errored: %s", java);
+			break;
+
+		case WARNING:
+			project.warning("Launch had a warning %s", java);
+			break;
+		default:
+			project.warning("Unknown code %d from launcher: %s", result, java);
+			break;
+		}
+	}
+
+	public void setTimeout(long timeout, TimeUnit unit) {
+		this.timeout = unit.convert(timeout, TimeUnit.MILLISECONDS);
+	}
+
+	public long getTimeout() {
+		return this.timeout;
+	}
+
+	public void cancel() {
+		java.cancel();
+	}
+
+	public Map<String, Map<String, String>> getSystemPackages() {
+		return runsystempackages;
+	}
+
+	public void setKeep(boolean keep) {
+		this.keep = keep;
+	}
+
+	public boolean isKeep() {
+		return keep;
+	}
+
+	public void setTrace(boolean level) {
+		this.trace = level;
+	}
+
+	public boolean getTrace() {
+		return this.trace;
+	}
+
+	/**
+	 * Should be called when all the changes to the launchers are set. Will
+	 * calculate whatever is necessary for the launcher.
+	 * 
+	 * @throws Exception
+	 */
+	public abstract void prepare() throws Exception;
+
+	public Project getProject() {
+		return project;
+	}
+
+	public boolean addActivator(String e) {
+		return activators.add(e);
+	}
+
+	public Collection<String> getActivators() {
+		return Collections.unmodifiableCollection(activators);
+	}
+
+	/**
+	 * Either NONE or SERVICES to indicate how the remote end launches. NONE
+	 * means it should not use the classpath to run a framework. This likely
+	 * requires some dummy framework support. SERVICES means it should load the
+	 * framework from the claspath.
+	 * 
+	 * @return
+	 */
+	public int getRunFramework() {
+		return framework;
+	}
+
+	public void setRunFramework(int n) {
+		assert n == NONE || n == SERVICES;
+		this.framework = n;
+	}
+
+	/**
+	 * Add the specification for a set of bundles the runpath if it does not
+	 * already is included. This can be used by subclasses to ensure the proper
+	 * jars are on the classpath.
+	 * 
+	 * @param defaultSpec
+	 *            The default spec for default jars
+	 */
+	public void addDefault(String defaultSpec) throws Exception {
+		Collection<Container> deflts = project.getBundles(Strategy.HIGHEST, defaultSpec, null);
+		for (Container c : deflts)
+			addClasspath(c);
+	}
+
+	/**
+	 * Create a self executable.
+	 */
+
+	public Jar executable() throws Exception {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java b/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java
new file mode 100644
index 0000000..5060d2f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java
@@ -0,0 +1,75 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+
+public abstract class ProjectTester {
+	final Project				project;
+	final Collection<Container>	testbundles;
+	final ProjectLauncher		launcher;
+	final List<String>			tests		= new ArrayList<String>();
+	File						reportDir;
+	boolean						continuous	= true;
+	
+	public ProjectTester(Project project) throws Exception {
+		this.project = project;
+		launcher = project.getProjectLauncher();
+		testbundles = project.getTestpath();
+		for (Container c : testbundles) {
+			launcher.addClasspath(c);
+		}
+		reportDir = new File(project.getTarget(), project.getProperty("test-reports",
+				"test-reports"));
+	}
+
+	public ProjectLauncher getProjectLauncher() {
+		return launcher;
+	}
+
+	public void addTest(String test) {
+		tests.add(test);
+	}
+
+	public Collection<String> getTests() {
+		return tests;
+	}
+
+	public Collection<File> getReports() {
+		List<File> reports = new ArrayList<File>();
+		for (File report : reportDir.listFiles()) {
+			if (report.isFile() )
+				reports.add(report);
+		}
+		return reports;
+	}
+
+	public File getReportDir() {
+		return reportDir;
+	}
+
+	public void setReportDir(File reportDir) {
+		this.reportDir = reportDir;
+	}
+
+	public Project getProject() {
+		return project;
+	}
+
+	public boolean getContinuous() {
+		return continuous;
+	}
+
+	public void setContinuous(boolean b) {
+		this.continuous = b;
+	}
+
+	public boolean prepare() throws Exception {
+		reportDir.mkdirs();
+		for ( File file : reportDir.listFiles() ) {
+			file.delete();
+		}
+		return true;
+	}
+
+	public abstract int test() throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java b/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java
new file mode 100644
index 0000000..55c2861
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java
@@ -0,0 +1,22 @@
+package aQute.bnd.build;
+
+import java.lang.reflect.*;
+
+import aQute.bnd.service.action.*;
+
+public class ReflectAction implements Action {
+    String  what;
+    
+    public ReflectAction(String what) {
+        this.what = what;
+    }
+    
+    public void execute(Project project, String action) throws Exception {
+        Method m = project.getClass().getMethod(what);
+        m.invoke(project);
+    }
+
+    public String toString() {
+        return "ra:" + what;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java b/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java
new file mode 100644
index 0000000..1a98e76
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java
@@ -0,0 +1,5 @@
+package aQute.bnd.build;
+
+public enum ResolverMode {
+	build, runtime
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java b/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java
new file mode 100644
index 0000000..8ca55fd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java
@@ -0,0 +1,18 @@
+package aQute.bnd.build;
+
+import aQute.bnd.service.action.*;
+
+public class ScriptAction implements Action {
+    final String script;
+    final String type;
+    
+    public ScriptAction(String type, String script) {
+        this.script = script;
+        this.type = type;
+    }
+
+    public void execute(Project project, String action) throws Exception {
+        project.script(type, script);
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java b/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
new file mode 100644
index 0000000..ed8a94e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
@@ -0,0 +1,294 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.lang.ref.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+import java.util.jar.*;
+
+import javax.naming.*;
+
+import aQute.bnd.maven.support.*;
+import aQute.bnd.service.*;
+import aQute.bnd.service.action.*;
+import aQute.lib.deployer.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+
+public class Workspace extends Processor {
+	public static final String					BUILDFILE	= "build.bnd";
+	public static final String					CNFDIR		= "cnf";
+	public static final String					BNDDIR		= "bnd";
+	public static final String					CACHEDIR	= "cache";
+
+	static Map<File, WeakReference<Workspace>>	cache		= newHashMap();
+	final Map<String, Project>					models		= newHashMap();
+	final Map<String, Action>					commands	= newMap();
+	final CachedFileRepo						cachedRepo;
+	final File									buildDir;
+	final Maven									maven		= new Maven(Processor.getExecutor());
+	private boolean	postpone;
+
+	/**
+	 * This static method finds the workspace and creates a project (or returns
+	 * an existing project)
+	 * 
+	 * @param projectDir
+	 * @return
+	 */
+	public static Project getProject(File projectDir) throws Exception {
+		projectDir = projectDir.getAbsoluteFile();
+		assert projectDir.isDirectory();
+
+		Workspace ws = getWorkspace(projectDir.getParentFile());
+		return ws.getProject(projectDir.getName());
+	}
+
+	public static Workspace getWorkspace(File parent) throws Exception {
+		File workspaceDir = parent.getAbsoluteFile();
+
+		// the cnf directory can actually be a
+		// file that redirects
+		while (workspaceDir.isDirectory()) {
+			File test = new File(workspaceDir, CNFDIR);
+
+			if (!test.exists())
+				test = new File(workspaceDir, BNDDIR);
+
+			if (test.isDirectory())
+				break;
+
+			if (test.isFile()) {
+				String redirect = IO.collect(test).trim();
+				test = getFile(test.getParentFile(), redirect).getAbsoluteFile();
+				workspaceDir = test;
+			}
+			if (!test.exists())
+				throw new IllegalArgumentException("No Workspace found from: " + parent);
+		}
+
+		synchronized (cache) {
+			WeakReference<Workspace> wsr = cache.get(workspaceDir);
+			Workspace ws;
+			if (wsr == null || (ws = wsr.get()) == null) {
+				ws = new Workspace(workspaceDir);
+				cache.put(workspaceDir, new WeakReference<Workspace>(ws));
+			}
+			return ws;
+		}
+	}
+
+	public Workspace(File dir) throws Exception {
+		dir = dir.getAbsoluteFile();
+		dir.mkdirs();
+		assert dir.isDirectory();
+
+		File buildDir = new File(dir, BNDDIR).getAbsoluteFile();
+		if (!buildDir.isDirectory())
+			buildDir = new File(dir, CNFDIR).getAbsoluteFile();
+
+		this.buildDir = buildDir;
+
+		File buildFile = new File(buildDir, BUILDFILE).getAbsoluteFile();
+		if (!buildFile.isFile())
+			warning("No Build File in " + dir);
+
+		setProperties(buildFile, dir);
+		propertiesChanged();
+		
+		cachedRepo = new CachedFileRepo();
+	}
+
+	public Project getProject(String bsn) throws Exception {
+		synchronized (models) {
+			Project project = models.get(bsn);
+			if (project != null)
+				return project;
+
+			File projectDir = getFile(bsn);
+			project = new Project(this, projectDir);
+			if (!project.isValid())
+				return null;
+
+			models.put(bsn, project);
+			return project;
+		}
+	}
+
+	public boolean isPresent(String name) {
+		return models.containsKey(name);
+	}
+
+	public Collection<Project> getCurrentProjects() {
+		return models.values();
+	}
+
+	public boolean refresh() {
+		if (super.refresh()) {
+			for (Project project : getCurrentProjects()) {
+				project.propertiesChanged();
+			}
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	public void propertiesChanged() {
+		super.propertiesChanged();
+		File extDir = new File(this.buildDir, "ext");
+		File[] extensions = extDir.listFiles();
+		if (extensions != null) {
+			for (File extension : extensions) {
+				if (extension.getName().endsWith(".bnd")) {
+					try {
+						doIncludeFile(extension, true, getProperties());
+					} catch (Exception e) {
+						error("PropertiesChanged: " + e.getMessage());
+					}
+				}
+			}
+		}
+	}
+
+	public String _workspace(String args[]) {
+		return getBase().getAbsolutePath();
+	}
+
+	public void addCommand(String menu, Action action) {
+		commands.put(menu, action);
+	}
+
+	public void removeCommand(String menu) {
+		commands.remove(menu);
+	}
+
+	public void fillActions(Map<String, Action> all) {
+		all.putAll(commands);
+	}
+
+	public Collection<Project> getAllProjects() throws Exception {
+		List<Project> projects = new ArrayList<Project>();
+		for (File file : getBase().listFiles()) {
+			if (new File(file, Project.BNDFILE).isFile())
+				projects.add(getProject(file));
+		}
+		return projects;
+	}
+
+	/**
+	 * Inform any listeners that we changed a file (created/deleted/changed).
+	 * 
+	 * @param f
+	 *            The changed file
+	 */
+	public void changedFile(File f) {
+		List<BndListener> listeners = getPlugins(BndListener.class);
+		for (BndListener l : listeners)
+			try {
+				l.changed(f);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+	}
+
+	public void bracket(boolean begin) {
+		List<BndListener> listeners = getPlugins(BndListener.class);
+		for (BndListener l : listeners)
+			try {
+				if ( begin )
+					l.begin();
+				else
+					l.end();
+			} catch (Exception e) {
+				// who cares?
+			}
+	}
+
+
+	private void copy(InputStream in, OutputStream out) throws Exception {
+		byte data[] = new byte[10000];
+		int size = in.read(data);
+		while (size > 0) {
+			out.write(data, 0, size);
+			size = in.read(data);
+		}
+	}
+
+	class CachedFileRepo extends FileRepo {
+		final Lock	lock	= new ReentrantLock();
+		boolean		inited;
+
+		CachedFileRepo() {
+			super("cache", getFile(buildDir, CACHEDIR), false);
+		}
+
+		protected void init() throws Exception {
+			if (lock.tryLock(50, TimeUnit.SECONDS) == false)
+				throw new TimeLimitExceededException(
+						"Cached File Repo is locked and can't acquire it");
+			try {
+				if (!inited) {
+					inited = true;
+					root.mkdirs();
+					if (!root.isDirectory())
+						throw new IllegalArgumentException("Cannot create cache dir " + root);
+
+					InputStream in = getClass().getResourceAsStream(EMBEDDED_REPO);
+					if (in != null)
+						unzip(in, root);
+					else
+						System.out.println("!!!! WTF Couldn't find embedded-repo.jar in bundle ");
+				}
+			} finally {
+				lock.unlock();
+			}
+		}
+
+		void unzip(InputStream in, File dir) throws Exception {
+			try {
+				JarInputStream jin = new JarInputStream(in);
+				JarEntry jentry = jin.getNextJarEntry();
+				while (jentry != null) {
+					if (!jentry.isDirectory()) {
+						File dest = Processor.getFile(dir, jentry.getName());
+						if (!dest.isFile() || dest.lastModified() < jentry.getTime()
+								|| jentry.getTime() == 0) {
+							dest.getParentFile().mkdirs();
+							FileOutputStream out = new FileOutputStream(dest);
+							try {
+								copy(jin, out);
+							} finally {
+								out.close();
+							}
+						}
+					}
+					jentry = jin.getNextJarEntry();
+				}
+			} finally {
+				in.close();
+			}
+		}
+	}
+
+	public List<RepositoryPlugin> getRepositories() {
+		return getPlugins(RepositoryPlugin.class);
+	}
+
+	public static Workspace getWorkspace(String path) throws Exception {
+		File file = IO.getFile(new File(""), path);
+		return getWorkspace(file);
+	}
+
+	public Maven getMaven() {
+		return maven;
+	}
+
+	@Override
+	protected void setTypeSpecificPlugins( Set<Object> list) {
+		super.setTypeSpecificPlugins(list);
+		list.add(maven);
+		list.add(cachedRepo);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/packageinfo b/bundleplugin/src/main/java/aQute/bnd/build/packageinfo
new file mode 100644
index 0000000..ec0efd4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/packageinfo
@@ -0,0 +1 @@
+version 1.43.1
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java
new file mode 100644
index 0000000..ac8bb37
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java
@@ -0,0 +1,25 @@
+package aQute.bnd.compatibility;
+
+import java.lang.reflect.*;
+
+/**
+ * Access modifier
+ */
+public enum Access {
+	PUBLIC, PROTECTED, PACKAGE, PRIVATE, UNKNOWN;
+
+	public static Access modifier(int mod) {
+		if (Modifier.isPublic(mod))
+			return PUBLIC;
+		if (Modifier.isProtected(mod))
+			return PROTECTED;
+		if (Modifier.isPrivate(mod))
+			return PRIVATE;
+
+		return PACKAGE;
+	}
+
+	public String toString() {
+		return super.toString().toLowerCase();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java
new file mode 100644
index 0000000..1eac28c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java
@@ -0,0 +1,25 @@
+package aQute.bnd.compatibility;
+
+public class GenericParameter {
+	String name;
+	GenericType bounds[];
+	
+	public GenericParameter(String name, GenericType[] bounds) {
+		this.name = name;
+		this.bounds = bounds;
+		if (bounds == null || bounds.length == 0)
+			bounds = new GenericType[] { new GenericType( Object.class) };
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(name);
+		if ( bounds != null && bounds.length > 0) {
+			for ( GenericType gtype : bounds ) {
+				sb.append( ":");
+				sb.append(gtype);
+			}
+		}
+		return sb.toString();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java
new file mode 100644
index 0000000..847a358
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java
@@ -0,0 +1,37 @@
+package aQute.bnd.compatibility;
+
+
+public class GenericType {
+	public GenericType(Class<Object> class1) {
+		// TODO Auto-generated constructor stub
+	}
+
+	final static GenericType	EMPTY[]	= new GenericType[0];
+	Scope						reference;
+	GenericType[]				a;
+	GenericType[]				b;
+	int							array;
+	
+	Scope	scope;
+	
+		static public class GenericWildcard extends GenericType{
+
+		public GenericWildcard(Class<Object> class1) {
+			super(class1);
+			// TODO Auto-generated constructor stub
+		}
+		
+	}
+	
+	static public class GenericArray extends GenericType {
+
+		public GenericArray(Class<Object> class1) {
+			super(class1);
+			// TODO Auto-generated constructor stub
+		}
+		
+	}
+	
+	
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java
new file mode 100644
index 0000000..1e84030
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java
@@ -0,0 +1,13 @@
+package aQute.bnd.compatibility;
+
+/**
+ * The kind of thing we scope
+ * 
+ */
+public enum Kind {
+	ROOT, CLASS, FIELD, CONSTRUCTOR, METHOD, UNKNOWN;
+
+	public String toString() {
+		return super.toString().toLowerCase();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/ParseSignatureBuilder.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/ParseSignatureBuilder.java
new file mode 100644
index 0000000..dc578ea
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/ParseSignatureBuilder.java
@@ -0,0 +1,109 @@
+package aQute.bnd.compatibility;
+
+import java.io.*;
+
+import aQute.lib.osgi.*;
+
+public class ParseSignatureBuilder {
+	final Scope			root;
+	
+	public ParseSignatureBuilder(Scope root) {
+		this.root = root;
+	}
+	
+	public void add( Jar jar ) throws Exception {
+		for ( Resource r : jar.getResources().values()) {
+			InputStream in = r.openInputStream();
+			try {
+				parse(in);
+			} finally {
+				in.close();
+			}
+		}
+	}
+	
+	public Scope getRoot() { return root; }
+	
+	
+	public void parse(InputStream in) throws IOException {
+		Clazz clazz = new Clazz("", null);
+		
+		clazz.parseClassFile(in, new ClassDataCollector() {
+			Scope	s;
+			Scope	enclosing;
+			Scope	declaring;
+
+			public void classBegin(int access, String name) {
+				s = root.getScope(Scope.classIdentity(name));
+				s.access = Access.modifier(access);
+				s.kind = Kind.CLASS;
+			}
+
+			public void extendsClass(String name) {
+//				s.setBase(new GenericType(name));
+			}
+
+			public void implementsInterfaces(String names[]) {
+				s.setParameterTypes(convert(names));
+			}
+
+			GenericType[] convert(String names[]) {
+				GenericType tss[] = new GenericType[names.length];
+				for (int i = 0; i < names.length; i++) {
+//					tss[i] = new GenericType(names[i]);
+				}
+				return tss;
+			}
+
+			public void method(Clazz.MethodDef defined) {
+				String descriptor;
+				Kind kind;
+				if (defined.isConstructor()) {
+					descriptor = ":" + defined.descriptor;
+					kind = Kind.CONSTRUCTOR;
+				} else {
+					descriptor = defined.name + ":" + defined.descriptor;
+					kind = Kind.METHOD;
+				}
+				Scope m = s.getScope(descriptor);
+				m.access = Access.modifier(defined.access);
+				m.kind = kind;
+				m.declaring = s;
+				s.add(m);
+			}
+
+			public void field(Clazz.FieldDef defined) {
+				String descriptor = defined.name + ":" + defined.descriptor;
+				Kind kind = Kind.FIELD;
+				Scope m = s.getScope(descriptor);
+				m.access = Access.modifier(defined.access);
+				m.kind = kind;
+				m.declaring = s;
+				s.add(m);
+			}
+
+			public void classEnd() {
+				if (enclosing != null)
+					s.setEnclosing( enclosing );
+				if (declaring != null)
+					s.setDeclaring( declaring );				
+			}
+
+			public void enclosingMethod(String cName, String mName, String mDescriptor) {
+				enclosing = root.getScope(Scope.classIdentity(cName));
+				if (mName != null) {
+					enclosing = enclosing.getScope(Scope.methodIdentity(mName, mDescriptor));
+				}
+			}
+
+			public void innerClass(String innerClass, String outerClass, String innerName,
+					int innerClassAccessFlags) {
+				if (outerClass != null && innerClass != null && innerClass.equals(s.name))
+					declaring = root.getScope(Scope.classIdentity(outerClass));
+			}
+		});
+		
+		
+	}
+}
+
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/RuntimeSignatureBuilder.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/RuntimeSignatureBuilder.java
new file mode 100644
index 0000000..7761701
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/RuntimeSignatureBuilder.java
@@ -0,0 +1,220 @@
+package aQute.bnd.compatibility;
+
+import java.lang.reflect.*;
+
+public class RuntimeSignatureBuilder {
+	final Scope	root;
+
+	public RuntimeSignatureBuilder(Scope root) {
+		this.root = root;
+	}
+
+	static public String identity(Class<?> c) {
+		return Scope.classIdentity(c.getName());
+	}
+
+	static public String identity(Method m) {
+		return Scope.methodIdentity(m.getName(), getDescriptor(m.getReturnType(), m
+				.getParameterTypes()));
+	}
+
+	static public String identity(Constructor m) {
+		return Scope.constructorIdentity(getDescriptor(void.class, m.getParameterTypes()));
+	}
+
+	static public String identity(Field m) {
+		return Scope.fieldIdentity(m.getName(), getDescriptor(m.getType(), null));
+	}
+
+	static public String getDescriptor(Class<?> base, Class<?>[] parameters) {
+		StringBuilder sb = new StringBuilder();
+		if (parameters != null) {
+			sb.append("(");
+			for (Class<?> parameter : parameters) {
+				sb.append(getDescriptor(parameter));
+			}
+			sb.append(")");
+		}
+		sb.append(getDescriptor(base));
+		return sb.toString();
+	}
+
+	public Scope add(Class<?> c) {
+		Scope local = add(root, getEnclosingScope(c), c.getModifiers(), c.getTypeParameters(),
+				Kind.CLASS, identity(c), c.getGenericSuperclass(), c.getGenericInterfaces(), null);
+
+		for (Field f : c.getDeclaredFields()) {
+			add(local, // declaring scope
+					local, // enclosing
+					f.getModifiers(), // access modifiers
+					null, // fields have no type vars
+					Kind.FIELD, // field
+					identity(f), // the name of the field
+					f.getGenericType(), // the type of the field
+					null, // fields have no parameters
+					null // fields have no exceptions
+			);
+		}
+
+		for (Constructor constr : c.getConstructors()) {
+			add(local, // class scope
+					local, // enclosing
+					constr.getModifiers(), // access modifiers
+					constr.getTypeParameters(), // Type vars
+					Kind.CONSTRUCTOR, // constructor
+					identity(constr), // <init>(type*)
+					void.class, // Always void
+					constr.getGenericParameterTypes(), // parameters types
+					constr.getGenericExceptionTypes() // exception types
+			);
+		}
+
+		for (Method m : c.getDeclaredMethods()) {
+			if (m.getDeclaringClass() != Object.class) {
+				add(local, // class scope
+						local, // enclosing
+						m.getModifiers(), // access modifiers
+						m.getTypeParameters(), Kind.METHOD, // method
+						identity(m), // <name>(type*)return
+						m.getGenericReturnType(), // return type
+						m.getGenericParameterTypes(), // parameter types
+						m.getGenericExceptionTypes() // exception types
+				);
+			}
+		}
+
+		return local;
+	}
+
+	private Scope getEnclosingScope(Class<?> c) {
+		Method m = c.getEnclosingMethod();
+		if (m != null) {
+			Scope s = getGlobalScope(m.getDeclaringClass());
+			return s.getScope(identity(m));
+		}
+// TODO
+//		Constructor cnstr = c.getEnclosingConstructor();
+//		if (m != null) {
+//			Scope s = getGlobalScope(cnstr.getDeclaringClass());
+//			return s.getScope(identity(cnstr));
+//
+//		}
+		Class<?> enclosingClass = c.getEnclosingClass();
+		if (enclosingClass != null) {
+			return getGlobalScope(enclosingClass);
+		}
+
+		return null;
+	}
+
+	private Scope getGlobalScope(Class<?> c) {
+		if (c == null)
+			return null;
+		String id = identity(c);
+		return root.getScope(id);
+	}
+
+	private Scope add(Scope declaring, Scope enclosing, int modifiers,
+			TypeVariable<?>[] typeVariables, Kind kind, String id, Type mainType,
+			Type[] parameterTypes, Type exceptionTypes[]) {
+
+		Scope scope = declaring.getScope(id);
+		assert scope.access == Access.UNKNOWN;
+		scope.setAccess(Access.modifier(modifiers));
+		scope.setKind(kind);
+		scope.setGenericParameter(convert(typeVariables));
+		scope.setBase(convert(scope,mainType));
+		scope.setParameterTypes(convert(parameterTypes));
+		scope.setExceptionTypes(convert(exceptionTypes));
+		scope.setDeclaring(declaring);
+		scope.setEnclosing(enclosing);
+		return scope;
+	}
+
+	private GenericType convert(Scope source, Type t) {
+		if (t instanceof ParameterizedType) {
+			// C<P..>
+			ParameterizedType pt = (ParameterizedType) t;
+			/*Scope reference =*/ root.getScope(identity((Class<?>)pt.getRawType()));			
+			Type args[] = pt.getActualTypeArguments();
+			GenericType[] arguments = new GenericType[args.length];
+			int n = 0;
+			for (Type arg : args)
+				arguments[n++] = convert(source,arg);
+//			return new GenericType(reference,null,arguments);
+			
+		} else if (t instanceof TypeVariable) {
+//			TypeVariable tv = (TypeVariable) t;
+//			return new GenericType(source,tv.getName(), null);
+		} else if (t instanceof WildcardType) {
+//			WildcardType wc = (WildcardType) t;
+//			wc.
+		} else if (t instanceof GenericArrayType) {
+
+		}
+		if (t instanceof Class<?>) {
+//			raw = ((Class<?>) t).getName() + ";";
+		} else
+			throw new IllegalArgumentException(t.toString());
+
+		return null;
+	}
+
+	private GenericParameter[] convert(TypeVariable vars[]) {
+		if (vars == null)
+			return null;
+
+		GenericParameter out[] = new GenericParameter[vars.length];
+		for (int i = 0; i < vars.length; i++) {
+			GenericType gss[] = convert(vars[i].getBounds());
+			out[i] = new GenericParameter(vars[i].getName(), gss);
+		}
+		return out;
+	}
+
+	private GenericType[] convert(Type[] parameterTypes) {
+		if (parameterTypes == null || parameterTypes.length == 0)
+			return GenericType.EMPTY;
+
+		GenericType tss[] = new GenericType[parameterTypes.length];
+		for (int i = 0; i < parameterTypes.length; i++) {
+			//tss[i] = new GenericType(parameterTypes[i]);
+		}
+		return tss;
+	}
+
+	private static String getDescriptor(Class<?> c) {
+		StringBuilder sb = new StringBuilder();
+		if (c.isPrimitive()) {
+			if (c == boolean.class)
+				sb.append("Z");
+			else if (c == byte.class)
+				sb.append("Z");
+			else if (c == char.class)
+				sb.append("C");
+			else if (c == short.class)
+				sb.append("S");
+			else if (c == int.class)
+				sb.append("I");
+			else if (c == long.class)
+				sb.append("J");
+			else if (c == float.class)
+				sb.append("F");
+			else if (c == double.class)
+				sb.append("D");
+			else if (c == void.class)
+				sb.append("V");
+			else
+				throw new IllegalArgumentException("unknown primitive type: " + c);
+		} else if (c.isArray()) {
+			sb.append("[");
+			sb.append(getDescriptor(c));
+		} else {
+			sb.append("L");
+			sb.append(c.getName().replace('.', '/'));
+			sb.append(";");
+		}
+		return sb.toString();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/Scope.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/Scope.java
new file mode 100644
index 0000000..218a31c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/Scope.java
@@ -0,0 +1,166 @@
+package aQute.bnd.compatibility;
+
+import java.io.*;
+import java.util.*;
+
+public class Scope {
+	final Map<String, Scope>	children	= new LinkedHashMap<String, Scope>();
+
+	// class: slashed name
+	// field: name ":" typed
+	// constructor: ":(" typed* ")" typed
+	// method: name ":(" typed* ")" typed
+	final String				name;
+
+	Access						access;
+	Kind						kind;
+	Scope						enclosing;
+	Scope						declaring;
+	GenericParameter						typeVars[];
+	Map<String, String[]>		name2bounds;
+
+	// class: super
+	// field: type
+	// constructor: void
+	// method: return
+	GenericType				base;
+
+	// class: interfaces
+	// constructor: args
+	// method: args
+	GenericType[]				parameters;
+
+	// constructor: exceptions
+	// method: exceptions
+	GenericType[]				exceptions;
+
+	// class: super interfaces*
+	// field: type
+	// constructor: void arguments*
+	// method: return arguments*
+
+	public Scope(Access access, Kind kind, String name) {
+		this.access = access;
+		this.kind = kind;
+		this.name = name;
+	}
+
+	Scope getScope(String name) {
+		Scope s = children.get(name);
+		if (s != null)
+			return s;
+
+		s = new Scope(Access.UNKNOWN, Kind.UNKNOWN, name);
+		children.put(name, s);
+		s.declaring = this;
+		return s;
+	}
+
+	public void setParameterTypes(GenericType[] convert) {
+		this.parameters = convert;
+	}
+
+	public void setExceptionTypes(GenericType[] convert) {
+		this.exceptions = convert;
+	}
+
+	public void setBase(GenericType typeSignature) {
+		base = typeSignature;
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		
+		if ( typeVars != null && typeVars.length !=0) {
+			sb.append("<");
+			for ( GenericParameter v : typeVars) {
+				sb.append(v);
+			}
+			sb.append(">");
+		}
+		sb.append(access.toString());
+		sb.append(" ");
+		sb.append(kind.toString());
+		sb.append( " ");
+		sb.append( name );
+		return sb.toString();
+	}
+
+	public void report(Appendable a, int indent) throws IOException {
+		for (int i = 0; i < indent; i++)
+			a.append("  ");
+		a.append(toString());
+		a.append("\n");
+		for (Scope s : children.values())
+			s.report(a, indent + 1);
+	}
+
+	public void add(Scope m) {
+		children.put(m.name, m);
+
+	}
+
+	public void setDeclaring(Scope declaring) {
+		this.declaring = declaring;
+	}
+
+	public void setAccess(Access access) {
+		this.access = access;
+	}
+
+	public void setEnclosing(Scope enclosing) {
+		this.enclosing = enclosing;
+		if (this.enclosing != null) {
+			this.enclosing.add(this);
+		}
+	}
+
+	public boolean isTop() {
+		return enclosing == null;
+	}
+
+	public void setKind(Kind kind) {
+		this.kind = kind;
+	}
+
+	static public String classIdentity(String name) {
+		return name.replace('.', '/');
+	}
+
+	static public String methodIdentity(String name, String descriptor) {
+		return name + ":" + descriptor;
+	}
+
+	static public String constructorIdentity(String descriptor) {
+		return ":" + descriptor;
+	}
+
+	static public String fieldIdentity(String name, String descriptor) {
+		return name + ":" + descriptor;
+	}
+
+	public void cleanRoot() {
+		Iterator<Map.Entry<String, Scope>> i = children.entrySet().iterator();
+		while (i.hasNext()) {
+			Map.Entry<String, Scope> entry = i.next();
+			if (!entry.getValue().isTop())
+				i.remove();
+		}
+	}
+
+	public void prune(EnumSet<Access> level) {
+		Iterator<Map.Entry<String, Scope>> i = children.entrySet().iterator();
+		while (i.hasNext()) {
+			Map.Entry<String, Scope> entry = i.next();
+			if (!level.contains(entry.getValue().access))
+				i.remove();
+			else
+				entry.getValue().prune(level);
+		}
+	}
+
+	public void setGenericParameter(GenericParameter[] typeVars) {
+		this.typeVars = typeVars;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/SignatureGenerator.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/SignatureGenerator.java
new file mode 100644
index 0000000..25177df
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/SignatureGenerator.java
@@ -0,0 +1,125 @@
+package aQute.bnd.compatibility;
+
+
+public class SignatureGenerator {
+//	enum ACCESS {
+////		PUBLIC("+"), PROTECTED("|"), PACKAGE_PRIVATE(""), PRIVATE("-");
+////		final String	repr;
+////
+////		ACCESS(String s) {
+////			repr = s;
+////		}
+////		
+////		public String toString() {
+////			return repr;
+////		}
+//	}
+//
+//	public static void main(String args[]) throws Exception {
+//		final PrintStream out = System.out;
+//
+//		Clazz c = new Clazz("x", new FileResource(new File(
+//				"src/aQute/bnd/compatibility/SignatureGenerator.class")));
+//		c.parseClassFileWithCollector(new ClassDataCollector() {
+//			public void classBegin(int access, String name) {
+//				out.print(name);
+//				out.println(access(access));
+//			}
+//
+//			private ACCESS access(int access) {
+//				if (Modifier.isPublic(access))
+//					return ACCESS.PUBLIC;
+//
+//				throw new IllegalArgumentException();
+//			}
+//
+//			public void extendsClass(String name) {
+//			}
+//
+//			public void implementsInterfaces(String name[]) {
+//			}
+//
+//			public void addReference(String token) {
+//			}
+//
+//			public void annotation(Annotation annotation) {
+//			}
+//
+//			public void parameter(int p) {
+//			}
+//
+//			public void method(Clazz.MethodDef defined) {
+//				if (defined.isConstructor())
+//					constructor(defined.access, defined.descriptor);
+//				else
+//					method(defined.access, defined.name, defined.descriptor);
+//			}
+//
+//			public void field(Clazz.FieldDef defined) {
+//				field(defined.access, defined.name, defined.descriptor);
+//			}
+//
+//			public void reference(Clazz.MethodDef referenced) {
+//			}
+//
+//			public void reference(Clazz.FieldDef referenced) {
+//			}
+//
+//			public void classEnd() {
+//			}
+//
+//			@Deprecated// Will really be removed!
+//			public void field(int access, String name, String descriptor) {
+//			}
+//
+//			@Deprecated// Will really be removed!
+//			public void constructor(int access, String descriptor) {
+//			}
+//
+//			@Deprecated// Will really be removed!
+//			public void method(int access, String name, String descriptor) {
+//			}
+//
+//			/**
+//			 * The EnclosingMethod attribute
+//			 * 
+//			 * @param cName
+//			 *            The name of the enclosing class, never null. Name is
+//			 *            with slashes.
+//			 * @param mName
+//			 *            The name of the enclosing method in the class with
+//			 *            cName or null
+//			 * @param mDescriptor
+//			 *            The descriptor of this type
+//			 */
+//			public void enclosingMethod(String cName, String mName, String mDescriptor) {
+//
+//			}
+//
+//			/**
+//			 * The InnerClass attribute
+//			 * 
+//			 * @param innerClass
+//			 *            The name of the inner class (with slashes). Can be
+//			 *            null.
+//			 * @param outerClass
+//			 *            The name of the outer class (with slashes) Can be
+//			 *            null.
+//			 * @param innerName
+//			 *            The name inside the outer class, can be null.
+//			 * @param modifiers
+//			 *            The access flags
+//			 */
+//			public void innerClass(String innerClass, String outerClass, String innerName,
+//					int innerClassAccessFlags) {
+//			}
+//
+//			public void signature(String signature) {
+//			}
+//
+//			public void constant(Object object) {
+//			}
+//
+//		});
+//	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/compatibility/Signatures.java b/bundleplugin/src/main/java/aQute/bnd/compatibility/Signatures.java
new file mode 100644
index 0000000..ec4a545
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/Signatures.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright (c) OSGi Alliance (2009, 2010). All Rights Reserved.
+ * 
+ * Licensed 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 aQute.bnd.compatibility;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * This class is compiled against 1.5 or later to provide access to the generic
+ * signatures. It can convert a Class, Field, Method or constructor to a generic
+ * signature and it can normalize a signature. Both are methods. Normalized
+ * signatures can be string compared and match even if the type variable names
+ * differ.
+ * 
+ * @version $Id$
+ */
+public class Signatures {
+	
+	
+	/**
+	 * Check if the environment has generics, i.e. later than 
+	 * Java 5 VM.
+	 * 
+	 * @return true if generics are supported
+	 * @throws Exception
+	 */
+	public boolean hasGenerics() throws Exception {
+		try {
+			call( Signatures.class, "getGenericSuperClass");
+			return true;
+		} catch( NoSuchMethodException mnfe ) {
+			return false;
+		}
+	}
+	
+	
+	
+	/**
+	 * Helper class to track an index in a string.
+	 */
+	class Rover {
+		final String	s;
+		int				i;
+
+		public Rover(String s) {
+			this.s = s;
+			i = 0;
+		}
+
+		char peek() {
+			return s.charAt(i);
+		}
+
+		char take() {
+			return s.charAt(i++);
+		}
+
+		char take(char c) {
+			char x = s.charAt(i++);
+			if (c != x)
+				throw new IllegalStateException("get() expected " + c
+						+ " but got + " + x);
+			return x;
+		}
+
+		public String upTo(String except) {
+			int start = i;
+			while (except.indexOf(peek()) < 0)
+				take();
+			return s.substring(start, i);
+		}
+
+		public boolean isEOF() {
+			return i >= s.length();
+		}
+
+	}
+
+	/**
+	 * Calculate the generic signature of a Class,Method,Field, or Constructor.
+	 * @param f
+	 * @return
+	 * @throws Exception 
+	 */
+	public String getSignature(Object c) throws Exception {
+		if( c instanceof Class<?>)
+			return getSignature((Class<?>)c);
+		if( c instanceof Constructor<?>)
+			return getSignature((Constructor<?>)c);
+		if( c instanceof Method)
+			return getSignature((Method)c);
+		if( c instanceof Field)
+			return getSignature((Field)c);
+		
+		throw new IllegalArgumentException(c.toString());
+	}
+
+	/**
+	 * Calculate the generic signature of a Class. A Class consists of:
+	 * 
+	 * <pre>
+	 * 	  class        ::= declaration? reference reference*
+	 * </pre>
+	 * 
+	 * 
+	 * @param f
+	 * @return
+	 * @throws Exception 
+	 */
+	public String getSignature(Class< ? > c) throws Exception {
+		StringBuffer sb = new StringBuffer();
+		declaration(sb, c);
+		reference(sb, call(c, "getGenericSuperclass"));
+		for (Object type : (Object[]) call(c,"getGenericInterfaces")) {
+			reference(sb, type);
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Calculate the generic signature of a Method. A Method consists of:
+	 * 
+	 * <pre>
+	 *    method ::= declaration? '(' reference* ')' reference
+	 * </pre>
+	 * 
+	 * @param c
+	 * @return
+	 * @throws Exception 
+	 */
+	public String getSignature(Method m) throws Exception {
+		StringBuffer sb = new StringBuffer();
+		declaration(sb, m);
+		sb.append('(');
+		for (Object type : (Object[]) call(m,"getGenericParameterTypes")) {
+			reference(sb, type);
+		}
+		sb.append(')');
+		reference(sb, call(m,"getGenericReturnType"));
+		return sb.toString();
+	}
+
+	/**
+	 * Calculate the generic signature of a Constructor. A Constructor consists
+	 * of:
+	 * 
+	 * <pre>
+	 *    constructor ::= declaration? '(' reference* ')V'
+	 * </pre>
+	 * 
+	 * @param c
+	 * @return
+	 * @throws Exception 
+	 */
+	public String getSignature(Constructor< ? > c) throws Exception {
+		StringBuffer sb = new StringBuffer();
+		declaration(sb, c);
+		sb.append('(');
+		for (Object type : (Object[]) call(c,"getGenericParameterTypes")) {
+			reference(sb, type);
+		}
+		sb.append(')');
+		reference(sb, void.class);
+		return sb.toString();
+	}
+
+	/**
+	 * Calculate the generic signature of a Field. A Field consists of:
+	 * 
+	 * <pre>
+	 *    constructor ::= reference
+	 * </pre>
+	 * 
+	 * @param c
+	 * @return
+	 * @throws Exception 
+	 */
+	public String getSignature(Field f) throws Exception {
+		StringBuffer sb = new StringBuffer();
+		Object t = call(f,"getGenericType");
+		reference(sb, t);
+		return sb.toString();
+	}
+
+/**
+	 * Classes, Methods, or Constructors can have a declaration that provides
+	 * nested a scope for type variables. A Method/Constructor inherits
+	 * the type variables from its class and a class inherits its type variables
+	 * from its outer class. The declaration consists of the following
+	 * syntax:
+	 * <pre>
+	 *    declarations ::= '<' declaration ( ',' declaration )* '>'
+	 *    declaration  ::= identifier ':' declare
+	 *    declare      ::= types | variable 
+	 *    types        ::= ( 'L' class ';' )? ( ':' 'L' interface ';' )*
+	 *    variable     ::= 'T' id ';'
+	 * </pre>
+	 * 
+	 * @param sb
+	 * @param gd
+ * @throws Exception 
+	 */
+	private void declaration(StringBuffer sb, Object gd) throws Exception {
+		Object[] typeParameters = (Object[]) call(gd,"getTypeParameters");
+		if (typeParameters.length > 0) {
+			sb.append('<');
+			for (Object tv : typeParameters) {
+				sb.append( call(tv,"getName"));
+
+				Object[] bounds = (Object[]) call(tv,"getBounds");
+				if (bounds.length > 0 && isInterface(bounds[0])) {
+					sb.append(':');
+				}
+				for (int i = 0; i < bounds.length; i++) {
+					sb.append(':');
+					reference(sb, bounds[i]);
+				}
+			}
+			sb.append('>');
+		}
+	}
+
+	/**
+	 * Verify that the type is an interface.
+	 * 
+	 * @param type the type to check.
+	 * @return true if this is a class that is an interface or a Parameterized
+	 *         Type that is an interface
+	 * @throws Exception 
+	 */
+	private boolean isInterface(Object type) throws Exception {
+		if (type instanceof Class)
+			return (((Class< ? >) type).isInterface());
+
+		if ( isInstance(type.getClass(), "java.lang.reflect.ParameterizedType"))
+			return isInterface(call(type,"getRawType"));
+
+		return false;
+	}
+
+
+/**
+	 * This is the heart of the signature builder. A reference is used
+	 * in a lot of places. It referes to another type.
+	 * <pre>
+	 *   reference     ::= array | class | primitive | variable
+	 *   array         ::= '[' reference
+	 *   class         ::=  'L' body ( '.' body )* ';'
+	 *   body          ::=  id ( '<' ( wildcard | reference )* '>' )?
+	 *   variable      ::=  'T' id ';'
+	 *   primitive     ::= PRIMITIVE
+	 * </pre>
+	 * 
+	 * @param sb
+	 * @param t
+ * @throws Exception 
+	 */
+	private void reference(StringBuffer sb, Object t) throws Exception {
+
+		if ( isInstance(t.getClass(),"java.lang.reflect.ParameterizedType")) {
+			sb.append('L');
+			parameterizedType(sb, t);
+			sb.append(';');
+			return;
+		}
+		else
+			if ( isInstance(t.getClass(), "java.lang.reflect.GenericArrayType")) {
+				sb.append('[');
+				reference(sb, call(t,"getGenericComponentType"));
+			}
+			else
+				if ( isInstance(t.getClass(), "java.lang.reflect.WildcardType")) {
+					Object[] lowerBounds = (Object[]) call(t, "getLowerBounds");
+					Object[] upperBounds = (Object[]) call(t, "getUpperBounds");
+
+					if (upperBounds.length == 1
+							&& upperBounds[0] == Object.class)
+						upperBounds = new Object[0];
+
+					if (upperBounds.length != 0) {
+						// extend
+						for (Object upper : upperBounds) {
+							sb.append('+');
+							reference(sb, upper);
+						}
+					}
+					else
+						if (lowerBounds.length != 0) {
+							// super, can only be one by the language
+							for (Object lower : lowerBounds) {
+								sb.append('-');
+								reference(sb, lower);
+							}
+						}
+						else
+							sb.append('*');
+				}
+				else
+					if ( isInstance(t.getClass(),"java.lang.reflect.TypeVariable")) {
+						sb.append('T');
+						sb.append( call(t,"getName"));
+						sb.append(';');
+					}
+					else
+						if (t instanceof Class< ? >) {
+							Class< ? > c = (Class< ? >) t;
+							if (c.isPrimitive()) {
+								sb.append(primitive(c));
+							}
+							else {
+								sb.append('L');
+								String name = c.getName().replace('.', '/');
+								sb.append(name);
+								sb.append(';');
+							}
+						}
+	}
+
+	/**
+	 * Creates the signature for a Parameterized Type.
+	 * 
+	 * A Parameterized Type has a raw class and a set of type variables.
+	 * 
+	 * @param sb
+	 * @param pt
+	 * @throws Exception 
+	 */
+	private void parameterizedType(StringBuffer sb, Object pt) throws Exception {
+		Object owner = call(pt,"getOwnerType");
+		String name = ((Class< ? >) call(pt,"getRawType")).getName()
+				.replace('.', '/');
+		if (owner != null) {
+			if ( isInstance(owner.getClass(), "java.lang.reflect.ParameterizedType"))
+				parameterizedType(sb, owner);
+			else
+				sb.append(((Class< ? >) owner).getName().replace('.', '/'));
+			sb.append('.');
+			int n = name.lastIndexOf('$');
+			name = name.substring(n + 1);
+		}
+		sb.append(name);
+
+		sb.append('<');
+		for (Object parameterType : (Object[]) call(pt,"getActualTypeArguments")) {
+			reference(sb, parameterType);
+		}
+		sb.append('>');
+
+	}
+
+	/**
+	 * Handle primitives, these need to be translated to a single char.
+	 * 
+	 * @param type the primitive class
+	 * @return the single char associated with the primitive
+	 */
+	private char primitive(Class< ? > type) {
+		if (type == byte.class)
+			return 'B';
+		else
+			if (type == char.class)
+				return 'C';
+			else
+				if (type == double.class)
+					return 'D';
+				else
+					if (type == float.class)
+						return 'F';
+					else
+						if (type == int.class)
+							return 'I';
+						else
+							if (type == long.class)
+								return 'J';
+							else
+								if (type == short.class)
+									return 'S';
+								else
+									if (type == boolean.class)
+										return 'Z';
+									else
+										if (type == void.class)
+											return 'V';
+										else
+											throw new IllegalArgumentException(
+													"Unknown primitive type "
+															+ type);
+	}
+
+	/**
+	 * Normalize a signature to make sure the name of the variables are always
+	 * the same. We change the names of the type variables to _n, where n is an
+	 * integer. n is incremented for every new name and already used names are
+	 * replaced with the _n name.
+	 * 
+	 * @return a normalized signature
+	 */
+
+	public String normalize(String signature) {
+		StringBuffer sb = new StringBuffer();
+		Map<String, String> map = new HashMap<String, String>();
+		Rover rover = new Rover(signature);
+		declare(sb, map, rover);
+
+		if (rover.peek() == '(') {
+			// method or constructor
+			sb.append(rover.take('('));
+			while (rover.peek() != ')') {
+				reference(sb, map, rover, true);
+			}
+			sb.append(rover.take(')'));
+			reference(sb, map, rover, true); // return type
+		}
+		else {
+			// field or class
+			reference(sb, map, rover, true); // field type or super class
+			while (!rover.isEOF()) {
+				reference(sb, map, rover, true); // interfaces
+			}
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * The heart of the routine. Handle a reference to a type. Can be
+	 * an array, a class, a type variable, or a primitive.
+	 * 
+	 * @param sb
+	 * @param map
+	 * @param rover
+	 * @param primitivesAllowed
+	 */
+	private void reference(StringBuffer sb, Map<String, String> map,
+			Rover rover, boolean primitivesAllowed) {
+
+		char type = rover.take();
+		sb.append(type);
+
+		if (type == '[') {
+			reference(sb, map, rover, true);
+		}
+		else
+			if (type == 'L') {
+				String fqnb = rover.upTo("<;.");
+				sb.append(fqnb);
+				body(sb, map, rover);
+				while (rover.peek() == '.') {
+					sb.append(rover.take('.'));
+					sb.append(rover.upTo("<;."));
+					body(sb, map, rover);
+				}
+				sb.append(rover.take(';'));
+			}
+			else
+				if (type == 'T') {
+					String name = rover.upTo(";");
+					name = assign(map, name);
+					sb.append(name);
+					sb.append(rover.take(';'));
+				}
+				else {
+					if (!primitivesAllowed)
+						throw new IllegalStateException(
+								"Primitives are not allowed without an array");
+				}
+	}
+
+	/**
+	 * Because classes can be nested the body handles the part that can 
+	 * be nested, the reference handles the enclosing L ... ;
+	 * 
+	 * @param sb
+	 * @param map
+	 * @param rover
+	 */
+	private void body(StringBuffer sb, Map<String, String> map, Rover rover) {
+		if (rover.peek() == '<') {
+			sb.append(rover.take('<'));
+			while (rover.peek() != '>') {
+				switch (rover.peek()) {
+					case 'L' :
+					case '[' :
+						reference(sb, map, rover, false);
+						break;
+
+					case 'T' :
+						String name;
+						sb.append(rover.take('T')); // 'T'
+						name = rover.upTo(";");
+						sb.append(assign(map, name));
+						sb.append(rover.take(';'));
+						break;
+
+					case '+' : // extends
+					case '-' : // super
+						sb.append(rover.take());
+						reference(sb, map, rover, false);
+						break;
+
+					case '*' : // wildcard
+						sb.append(rover.take());
+						break;
+
+				}
+			}
+			sb.append(rover.take('>'));
+		}
+	}
+
+	/**
+	 * Handle the declaration part.
+	 * 
+	 * @param sb
+	 * @param map
+	 * @param rover
+	 */
+	private void declare(StringBuffer sb, Map<String, String> map, Rover rover) {
+		char c = rover.peek();
+		if (c == '<') {
+			sb.append(rover.take('<'));
+
+			while (rover.peek() != '>') {
+				String name = rover.upTo(":");
+				name = assign(map, name);
+				sb.append(name);
+				typeVar: while (rover.peek() == ':') {
+					sb.append(rover.take(':'));
+					switch (rover.peek()) {
+						case ':' : // empty class cases
+							continue typeVar;
+
+						default :
+							reference(sb, map, rover, false);
+							break;
+					}
+				}
+			}
+			sb.append(rover.take('>'));
+		}
+	}
+
+	/**
+	 * Handles the assignment of type variables to index names so that
+	 * we have a normalized name for each type var.
+	 * 
+	 * @param map the map with variables.
+	 * @param name The name of the variable
+	 * @return the index name, like _1
+	 */
+	private String assign(Map<String, String> map, String name) {
+		if (map.containsKey(name))
+			return map.get(name);
+		else {
+			int n = map.size();
+			map.put(name, "_" + n);
+			return "_" + n;
+		}
+	}
+
+	private boolean isInstance(Class<?> type, String string) {
+		if ( type == null)
+			return false;
+		
+		if ( type.getName().equals(string))
+			return true;
+		
+		if ( isInstance( type.getSuperclass(), string))
+			return true;
+		
+		for ( Class<?> intf : type.getInterfaces()) {
+			if ( isInstance(intf,string))
+				return true;
+		}
+		return false;
+	}
+	
+	private Object call(Object gd, String string) throws Exception {
+		Method m = gd.getClass().getMethod(string);
+		return m.invoke(gd);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/AnnotationReader.java b/bundleplugin/src/main/java/aQute/bnd/component/AnnotationReader.java
new file mode 100644
index 0000000..af927f4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/AnnotationReader.java
@@ -0,0 +1,344 @@
+package aQute.bnd.component;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.libg.version.*;
+
+public class AnnotationReader extends ClassDataCollector {
+	final static String[]			EMPTY					= new String[0];
+	final static Pattern			PROPERTY_PATTERN		= Pattern
+																	.compile("([^=]+(:(Boolean|Byte|Char|Short|Integer|Long|Float|Double|String))?)\\s*=(.*)");
+
+	public static final Version	V1_1					= new Version("1.1.0");																												// "1.1.0"
+	public static final Version	V1_2					= new Version("1.2.0");																												// "1.1.0"
+	static Pattern					BINDDESCRIPTOR			= Pattern
+																	.compile("\\(((L([^;]+);)(Ljava/util/Map;)?|Lorg/osgi/framework/ServiceReference;)\\)V");
+	static Pattern					BINDMETHOD				= Pattern
+																	.compile("(set|bind|add)?(.*)");
+
+	static Pattern					ACTIVATEDESCRIPTOR		= Pattern
+																	.compile("\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)V");
+	static Pattern					REFERENCEBINDDESCRIPTOR	= Pattern
+																	.compile("\\(Lorg/osgi/framework/ServiceReference;\\)V");
+
+	ComponentDef					component				= new ComponentDef();
+
+	Clazz							clazz;
+	String							interfaces[];
+	String							methodDescriptor;
+	String							method;
+	String							className;
+	int								methodAccess;
+	Analyzer						analyzer;
+	MultiMap<String, String>		methods					= new MultiMap<String, String>();
+	String							extendsClass;
+
+	AnnotationReader(Analyzer analyzer, Clazz clazz) {
+		this.analyzer = analyzer;
+		this.clazz = clazz;
+	}
+
+	public static ComponentDef getDefinition(Clazz c, Analyzer analyzer) throws Exception {
+		AnnotationReader r = new AnnotationReader(analyzer, c);
+
+		return r.getDef(c, analyzer);
+	}
+
+	/**
+	 * fixup any unbind methods To declare no unbind method, the value "-" must
+	 * be used. If not specified, the name of the unbind method is derived from
+	 * the name of the annotated bind method. If the annotated method name
+	 * begins with set, that is replaced with unset to derive the unbind method
+	 * name. If the annotated method name begins with add, that is replaced with
+	 * remove to derive the unbind method name. Otherwise, un is prefixed to the
+	 * annotated method name to derive the unbind method name.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	private ComponentDef getDef(Clazz c, Analyzer analyzer) throws Exception {
+		c.parseClassFileWithCollector(this);
+		if (component.implementation == null)
+			return null;
+
+		while (extendsClass != null) {
+			if (extendsClass.startsWith("java/"))
+				break;
+
+			Clazz ec = analyzer.findClass(extendsClass);
+			if (ec == null) {
+				analyzer.error("Missing super class for DS annotations: "
+						+ Clazz.pathToFqn(extendsClass) + " from " + c.getFQN());
+			} else {
+				c.parseClassFileWithCollector(this);
+			}
+		}
+
+		if (component.implementation != null) {
+			for (ReferenceDef rdef : component.references.values()) {
+				rdef.unbind = referredMethod(analyzer, rdef, rdef.unbind, "add(.*)", "remove$1", "(.*)",
+						"un$1");
+				rdef.modified = referredMethod(analyzer, rdef, rdef.modified, "(add|set)(.*)", "modified$2",
+						"(.*)", "modified$1");
+			}
+			return component;
+		} else
+			return null;
+	}
+
+	/**
+	 * 
+	 * @param analyzer
+	 * @param rdef
+	 */
+	protected String referredMethod(Analyzer analyzer, ReferenceDef rdef, String value,
+			String... matches) {
+		if (value == null) {
+			String bind = rdef.bind;
+			for (int i = 0; i < matches.length; i += 2) {
+				Matcher m = Pattern.compile(matches[i]).matcher(bind);
+				if (m.matches()) {
+					value = m.replaceFirst(matches[i+1]);
+					break;
+				}
+			}
+		} else if (value.equals("-"))
+			return null;
+
+		if (methods.containsKey(value)) {
+			for (String descriptor : methods.get(value)) {
+				Matcher matcher = BINDDESCRIPTOR.matcher(descriptor);
+				if (matcher.matches()) {
+					String type = matcher.group(2);
+					if (rdef.interfce.equals(Clazz.objectDescriptorToFQN(type))
+							|| type.equals("Ljava/util/Map;")
+							|| type.equals("Lorg/osgi/framework/ServiceReference;")) {
+
+						return value;
+					}
+				}
+			}
+			analyzer.error(
+					"A related method to %s from the reference %s has no proper prototype for class %s. Expected void %s(%s s [,Map m] | ServiceReference r)",
+					rdef.bind, value, component.implementation, value, rdef.interfce);
+		}
+		return null;
+	}
+
+	public void annotation(Annotation annotation) {
+		try {
+			java.lang.annotation.Annotation a = annotation.getAnnotation();
+
+			if (a instanceof Component)
+				doComponent((Component) a, annotation);
+			else if (a instanceof Activate)
+				doActivate();
+			else if (a instanceof Deactivate)
+				doDeactivate();
+			else if (a instanceof Modified)
+				doModified();
+			else if (a instanceof Reference)
+				doReference((Reference) a, annotation);
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			analyzer.error("During generation of a component on class %s, exception %s", clazz, e);
+		}
+	}
+
+	/**
+	 * 
+	 */
+	protected void doDeactivate() {
+		if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
+			analyzer.error(
+					"Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, methodDescriptor);
+		else {
+			component.deactivate = method;
+		}
+	}
+
+	/**
+	 * 
+	 */
+	protected void doModified() {
+		if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
+			analyzer.error(
+					"Modified method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, methodDescriptor);
+		else {
+			component.modified = method;
+		}
+	}
+	/**
+	 * @param annotation
+	 * @throws Exception
+	 */
+	protected void doReference(Reference reference, Annotation raw) throws Exception {
+		ReferenceDef def = new ReferenceDef();
+		def.name = reference.name();
+
+		if (def.name == null) {
+			Matcher m = BINDMETHOD.matcher(method);
+			if (m.matches()) {
+				def.name = m.group(2);
+			} else {
+				def.name = method;
+			}
+		}
+
+		def.unbind = reference.unbind();
+		def.bind = method;
+
+		def.interfce = raw.get("service");
+		if (def.interfce != null) {
+			def.interfce = Clazz.objectDescriptorToFQN(def.interfce);
+		} else {
+			// We have to find the type of the current method to
+			// link it to the referenced service.
+			Matcher m = BINDDESCRIPTOR.matcher(methodDescriptor);
+			if (m.matches()) {
+				def.interfce = Clazz.internalToFqn(m.group(3));
+			} else
+				throw new IllegalArgumentException(
+						"Cannot detect the type of a Component Reference from the descriptor: "
+								+ methodDescriptor);
+		}
+
+		// Check if we have a target, this must be a filter
+		def.target = reference.target();
+		if (def.target != null) {
+			Verifier.verifyFilter(def.target, 0);
+		}
+
+		if (component.references.containsKey(def.name))
+			analyzer.error(
+					"In component %s, multiple references with the same name: %s. Previous def: %s, this def: %s",
+					component.implementation, component.references.get(def.name), def.interfce, "");
+		else
+			component.references.put(def.name, def);
+
+		def.cardinality = reference.cardinality();
+		def.policy = reference.policy();
+	}
+
+	/**
+	 * 
+	 */
+	protected void doActivate() {
+		if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
+			analyzer.error(
+					"Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, methodDescriptor);
+		else {
+			component.activate = method;
+		}
+	}
+
+	/**
+	 * @param annotation
+	 * @throws Exception
+	 */
+	protected void doComponent(Component comp, Annotation annotation) throws Exception {
+
+		// Check if we are doing a super class
+		if (component.implementation != null)
+			return;
+
+		component.version = V1_1;
+		component.implementation = clazz.getFQN();
+		component.name = comp.name();
+		component.factory = comp.factory();
+		component.configurationPolicy = comp.configurationPolicy();
+		if (annotation.get("enabled") != null)
+			component.enabled = comp.enabled();
+		if (annotation.get("factory") != null)
+			component.factory = comp.factory();
+		if (annotation.get("immediate") != null)
+			component.immediate = comp.immediate();
+		if (annotation.get("servicefactory") != null)
+			component.servicefactory = comp.servicefactory();
+
+		String properties[] = comp.properties();
+		if (properties != null)
+			for (String entry : properties)
+				component.properties.add(entry);
+
+		doProperties(comp.property());
+		Object [] x = annotation.get("service");
+		
+		if (x == null) {
+			// Use the found interfaces, but convert from internal to
+			// fqn.
+			if (interfaces != null) {
+				List<String> result = new ArrayList<String>();
+				for (int i = 0; i < interfaces.length; i++) {
+					if (!interfaces[i].equals("scala/ScalaObject"))
+						result.add(Clazz.internalToFqn(interfaces[i]));
+				}
+				component.service = result.toArray(EMPTY);
+			}
+		} else {
+			// We have explicit interfaces set
+			component.service= new String[x.length];
+			for (int i = 0; i < x.length; i++) {
+				component.service[i] = Clazz.objectDescriptorToFQN(x[i].toString());
+			}
+		}
+
+	}
+
+	/**
+	 * Parse the properties
+	 */
+
+	private void doProperties(String[] properties) {
+		if (properties != null) {
+			for (String p : properties) {
+				Matcher m = PROPERTY_PATTERN.matcher(p);
+
+				if (m.matches()) {
+					String key = m.group(1);
+					String value = m.group(4);
+					component.property.add(key, value);
+				} else
+					throw new IllegalArgumentException("Malformed property '" + p
+							+ "' on component: " + className);
+			}
+		}
+	}
+
+	/**
+	 * Are called during class parsing
+	 */
+
+	@Override public void classBegin(int access, String name) {
+		className = name;
+	}
+
+	@Override public void implementsInterfaces(String[] interfaces) {
+		this.interfaces = interfaces;
+	}
+
+	@Override public void method(int access, String name, String descriptor) {
+		if (Modifier.isPrivate(access) || Modifier.isAbstract(access) || Modifier.isStatic(access))
+			return;
+
+		this.method = name;
+		this.methodDescriptor = descriptor;
+		this.methodAccess = access;
+		methods.add(name, descriptor);
+	}
+
+	@Override public void extendsClass(String name) {
+		this.extendsClass = name;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java b/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
new file mode 100644
index 0000000..30268a7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
@@ -0,0 +1,132 @@
+package aQute.bnd.component;
+
+import java.util.*;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+class ComponentDef {
+	final static String				NAMESPACE_STEM	= "http://www.osgi.org/xmlns/scr";
+	Version							version			= new Version("1.1.0");
+	String							name;
+	String							factory;
+	Boolean							immediate;
+	Boolean							servicefactory;
+	ConfigurationPolicy				configurationPolicy;
+	String							implementation;
+	String							service[];
+	String							activate;
+	String							deactivate;
+	String							modified;
+	Boolean							enabled;
+	final List<String>				properties		= new ArrayList<String>();
+	final MultiMap<String, String>	property		= new MultiMap<String, String>();
+	final Map<String, ReferenceDef>	references		= new TreeMap<String, ReferenceDef>();
+
+	void prepare(Analyzer analyzer) {
+
+		for (ReferenceDef ref : references.values()) {
+			ref.prepare(analyzer);
+			if (ref.version.compareTo(version) > 0)
+				version = ref.version;
+		}
+
+		if (implementation == null)
+			analyzer.error("No Implementation defined for component " + name);
+		else
+			analyzer.referTo(implementation);
+
+		name = implementation;
+
+		if (service != null && service.length > 0) {
+			for (String interfaceName : service)
+				analyzer.referTo(interfaceName);
+		} else if (servicefactory != null && servicefactory)
+			analyzer.warning("The servicefactory:=true directive is set but no service is provided, ignoring it");
+	}
+
+	Tag getTag() {
+		Tag component = new Tag("scr:component");
+		component.addAttribute("xmlns:scr", NAMESPACE_STEM + "/" + version);
+		component.addAttribute("name", name);
+
+		if (servicefactory != null)
+			component.addAttribute("servicefactory", servicefactory);
+
+		if (configurationPolicy != null)
+			component.addAttribute("configuration-policy", configurationPolicy.toString()
+					.toLowerCase());
+		if (enabled != null)
+			component.addAttribute("enabled", enabled);
+		if (immediate != null)
+			component.addAttribute("immediate", immediate);
+
+		if (factory != null)
+			component.addAttribute("factory", factory);
+
+		if (activate != null)
+			component.addAttribute("activate", activate);
+
+		if (deactivate != null)
+			component.addAttribute("deactivate", deactivate);
+
+		if (modified != null)
+			component.addAttribute("modified", modified);
+
+		Tag impl = new Tag(component, "implementation");
+		impl.addAttribute("class", implementation);
+
+		if (service != null && service.length != 0) {
+			Tag s = new Tag(component, "service");
+			if (servicefactory != null && servicefactory)
+				s.addAttribute("servicefactory", true);
+
+			for (String ss : service) {
+				Tag provide = new Tag(s, "provide");
+				provide.addAttribute("interface", ss);
+			}
+		}
+
+		for (ReferenceDef ref : references.values()) {
+			Tag refTag = ref.getTag();
+			component.addContent(refTag);
+
+		}
+
+		for (Map.Entry<String, Set<String>> kvs : property.entrySet()) {
+			Tag property = new Tag(component, "property");
+			String name = kvs.getKey();
+			String type = null;
+			int n = name.indexOf(':');
+			if (n > 0) {
+				type = name.substring(n + 1);
+				name = name.substring(0, n);
+			}
+
+			property.addAttribute("name", name);
+			if (type != null) {
+				property.addAttribute("type", type);
+			}
+			StringBuffer sb = new StringBuffer();
+
+			String del = "";
+			for (String v : kvs.getValue()) {
+				sb.append(del);
+				sb.append(v);
+				del = "\n";
+			}
+			property.addContent(sb.toString());
+		}
+
+		for (String entry : properties) {
+			Tag properties = new Tag(component, "properties");
+			properties.addAttribute("entry", entry);
+		}
+		return component;
+	}
+
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java b/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
new file mode 100644
index 0000000..a592377
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
@@ -0,0 +1,25 @@
+package aQute.bnd.component;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+/**
+ * Analyze the class space for any classes that have an OSGi annotation for DS.
+ * 
+ */
+public class DSAnnotations implements AnalyzerPlugin {
+
+	public boolean analyzeJar(Analyzer analyzer) throws Exception {
+
+		for (Clazz c : analyzer.getClassspace().values()) {
+			ComponentDef definition = AnnotationReader.getDefinition(c, analyzer);
+			if (definition != null) {
+				definition.prepare(analyzer);
+				analyzer.getJar().putResource("OSGI-INF/" + definition.name + ".xml",
+						new TagResource(definition.getTag()));
+			}
+		}
+		return false;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/ReferenceDef.java b/bundleplugin/src/main/java/aQute/bnd/component/ReferenceDef.java
new file mode 100644
index 0000000..ca810e6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/ReferenceDef.java
@@ -0,0 +1,98 @@
+package aQute.bnd.component;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+class ReferenceDef {
+	Version					version;
+	String					name;
+	String					interfce;
+	ReferenceCardinality	cardinality;
+	ReferencePolicy			policy;
+	String					target;
+	String					bind;
+	String					unbind;
+	String					modified;
+
+	public void prepare(Analyzer analyzer) {
+		if (name == null)
+			analyzer.error("No name for a reference");
+
+		if (version == null)
+			version = AnnotationReader.V1_1;
+
+	}
+
+	public Tag getTag() {
+		Tag ref = new Tag("reference");
+		ref.addAttribute("name", name);
+		if (cardinality != null)
+			ref.addAttribute("cardinality", p(cardinality));
+		if (policy != null)
+			ref.addAttribute("policy", p(policy));
+
+		if (interfce != null)
+			ref.addAttribute("interface", interfce);
+
+		if (target != null)
+			ref.addAttribute("target", target);
+
+		if (bind != null)
+			ref.addAttribute("bind", bind);
+
+		if (unbind != null)
+			ref.addAttribute("unbind", unbind);
+
+		if (modified != null) {
+			ref.addAttribute("modified", modified);
+			version = max(version, AnnotationReader.V1_2);
+		}
+
+		return ref;
+	}
+
+	private String p(ReferencePolicy policy) {
+		switch(policy) {
+		case DYNAMIC:
+			return "dynamic";
+			
+		case STATIC:
+			return "static";
+		}
+		return policy.toString();
+	}
+
+	private String p(ReferenceCardinality crd) {
+		switch (crd) {
+		case AT_LEAST_ONE:
+			return "1..n";
+
+		case MANDATORY:
+			return "1..1";
+
+		case MULTIPLE:
+			return "0..n";
+
+		case OPTIONAL:
+			return "0..1";
+
+		}
+		return crd.toString();
+	}
+
+	private <T extends Comparable<T>> T max(T a, T b) {
+		int n = a.compareTo(b);
+		if (n >= 0)
+			return a;
+		else
+			return b;
+	}
+
+	public String toString() {
+		return name;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/concurrent/MultiBuilder.java b/bundleplugin/src/main/java/aQute/bnd/concurrent/MultiBuilder.java
new file mode 100644
index 0000000..ad9708d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/concurrent/MultiBuilder.java
@@ -0,0 +1,141 @@
+package aQute.bnd.concurrent;
+
+import java.io.*;
+import java.util.*;
+
+import aQute.bnd.build.*;
+import aQute.lib.osgi.*;
+import aQute.libg.forker.*;
+
+/**
+ * This class implements a concurrent builder. It manages the build process in
+ * an environment where many threads can initiate builds. Users should call
+ * changed(Project,boolean)
+ * 
+ */
+public class MultiBuilder {
+	Workspace		workspace;
+	Forker<Project>	forker;
+	boolean			building		= false;
+	final Set<File>	filesChanged	= Collections.synchronizedSet(new HashSet<File>());
+
+	/**
+	 * Constructor
+	 * 
+	 * @param workspace
+	 *            the workspace this MultiBuilder works for.
+	 * 
+	 */
+	public MultiBuilder(Workspace workspace) {
+		this.workspace = workspace;
+	}
+
+	/**
+	 * Return the build result of a project.
+	 * 
+	 * @param p
+	 *            the project
+	 * @return the files build by the project
+	 * 
+	 * @throws Exception
+	 */
+	public File[] build(Project p) throws Exception {
+		if (p.isStale()) {
+			startBuild();
+		}
+		syncBuild();
+		return p.build();
+	}
+
+	/**
+	 * Indicate that the project has changed. This will start a build.
+	 * 
+	 * @param p
+	 *            the project that is changed
+	 * @throws Exception
+	 */
+	public void changed(Project p) throws Exception {
+		p.setChanged();
+		cancel();
+		startBuild();
+	}
+
+	/**
+	 * Cancel the current build or do nothing if no build is active.
+	 * 
+	 * @throws InterruptedException
+	 */
+	public synchronized void cancel() throws InterruptedException {
+		if (building) {
+			forker.cancel();
+		}
+	}
+
+	/**
+	 * Synchronize with a current build or return immediately.
+	 * 
+	 * @throws InterruptedException
+	 */
+	public synchronized void syncBuild() throws InterruptedException {
+		if (building) {
+			forker.join();
+		}
+	}
+
+	/**
+	 * Schedule a new build if no build is running otherwise return.
+	 * 
+	 * @throws Exception
+	 */
+	public void startBuild() throws Exception {
+		synchronized (this) {
+			if (building)
+				return;
+
+			forker = new Forker<Project>(Processor.getExecutor());
+			building = true;
+		}
+
+		Processor.getExecutor().execute(new Runnable() {
+			public void run() {
+				try {
+					build();
+					synchronized (MultiBuilder.this) {
+						building = false;
+					}
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		});
+	}
+
+	/**
+	 * Do the whole build using a forker.
+	 * 
+	 * @throws Exception
+	 */
+	private void build() throws Exception {
+		// handle multiple requests
+		Thread.sleep(100);
+		workspace.bracket(true);
+		try {
+			for (final Project p : workspace.getAllProjects()) {
+				forker.doWhen(p.getDependson(), p, new Runnable() {
+
+					public void run() {
+						try {
+							p.build();
+						} catch (Exception e) {
+							e.printStackTrace();
+						}
+					}
+				});
+			}
+			forker.join();
+		} finally {
+			workspace.bracket(false);
+		}
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/help/Errors.java b/bundleplugin/src/main/java/aQute/bnd/help/Errors.java
new file mode 100644
index 0000000..1d950ad
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/help/Errors.java
@@ -0,0 +1,5 @@
+package aQute.bnd.help;
+
+public interface Errors {
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/help/Syntax.java b/bundleplugin/src/main/java/aQute/bnd/help/Syntax.java
new file mode 100644
index 0000000..20a7440
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/help/Syntax.java
@@ -0,0 +1,481 @@
+package aQute.bnd.help;
+
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.lib.osgi.*;
+
+public class Syntax implements Constants {
+    final String                            header;
+    final String                            lead;
+    final String                            example;
+    final Pattern                           pattern;
+    final String                            values;
+    final Syntax[]                          children;
+
+    static Syntax                           version              = new Syntax(
+                                                                         VERSION_ATTRIBUTE,
+                                                                         "A version range to select the version of an export definition. The default value is 0.0.0 .",
+                                                                         "version=\"[1.2,3.0)\"",
+                                                                         null,
+                                                                         Verifier.VERSIONRANGE);
+    static Syntax                           bundle_symbolic_name = new Syntax(
+                                                                         BUNDLE_SYMBOLIC_NAME_ATTRIBUTE,
+                                                                         "The bundle symbolic name of the exporting bundle.",
+                                                                         "bundle-symbolic-name=com.acme.foo.daffy",
+                                                                         null,
+                                                                         Verifier.SYMBOLICNAME);
+
+    static Syntax                           bundle_version       = new Syntax(
+                                                                         BUNDLE_VERSION_ATTRIBUTE,
+                                                                         "a version range to select the bundle version of the exporting bundle. The default value is 0.0.0.",
+                                                                         "bundle-version=1.3",
+                                                                         null,
+                                                                         Verifier.VERSIONRANGE);
+
+    static Syntax                           path_version         = new Syntax(
+                                                                         VERSION_ATTRIBUTE,
+                                                                         "Specifies the range in the repository, project, or file",
+                                                                         "version=project",
+                                                                         "project,type",
+                                                                         Pattern
+                                                                                 .compile("project|type|"
+                                                                                         + Verifier.VERSIONRANGE
+                                                                                                 .toString()));
+
+    @SuppressWarnings("deprecation")
+	static Syntax[]                         syntaxes             = new Syntax[] {
+            new Syntax(
+                    BUNDLE_ACTIVATIONPOLICY,
+                    "The Bundle-ActivationPolicy specifies how the framework should activate the bundle once started. ",
+                    "Bundle-ActivationPolicy: lazy", "lazy", Pattern
+                            .compile("lazy")),
+
+            new Syntax(
+                    BUNDLE_ACTIVATOR,
+                    "The Bundle-Activator header specifies the name of the class used to start and stop the bundle. ",
+                    "Bundle-Activator: com.acme.foo.Activator",
+                    "${classes;implementing;org.osgi.framework.BundleActivator}",
+                    Verifier.FQNPATTERN),
+            new Syntax(
+                    BUNDLE_CATEGORY,
+                    "The Bundle-Category header holds a comma-separated list of category names",
+                    "Bundle-Category: test",
+                    "osgi,test,game,util,eclipse,netbeans,jdk,specification",
+                    null),
+            new Syntax(
+                    BUNDLE_CLASSPATH,
+                    "The Bundle-ClassPath header defines a comma-separated list of JAR file path names or directories (inside the bundle) containing classes and resources. The period (’.’) specifies the root directory of the bundle’s JAR. The period is also the default.",
+                    "Bundle-Classpath: /lib/libnewgen.so, .", null,
+                    Verifier.PATHPATTERN),
+            new Syntax(
+                    BUNDLE_CONTACTADDRESS,
+                    "The Bundle-ContactAddress header provides the contact address of the vendor. ",
+                    "Bundle-ContactAddress: 2400 Oswego Road, Austin, TX 74563",
+                    null, null),
+            new Syntax(
+                    BUNDLE_COPYRIGHT,
+                    "The Bundle-Copyright header contains the copyright specification for this bundle. ",
+                    "Bundle-Copyright: OSGi (c) 2002", null, null),
+            new Syntax(
+                    BUNDLE_DESCRIPTION,
+                    "The Bundle-Description header defines a short description of this bundle.",
+                    "Bundle-Description: Ceci ce n'est pas une bundle", null,
+                    null),
+
+            new Syntax(
+                    BUNDLE_DOCURL,
+                    "The Bundle-DocURL headers must contain a URL pointing to documentation about this bundle.",
+                    "Bundle-DocURL: http://www.aQute.biz/Code/Bnd", null,
+                    Verifier.URLPATTERN),
+
+            new Syntax(
+                    BUNDLE_ICON,
+                    "The optional Bundle-Icon header provides a list of (relative) URLs to icons representing this bundle in different sizes. ",
+                    "Bundle-Icon: /icons/bnd.png;size=64", "/icons/bundle.png",
+                    Verifier.URLPATTERN, new Syntax("size",
+                            "Icons size in pixels, e.g. 64", "64",
+                            "16,32,48,64,128", Verifier.NUMBERPATTERN)),
+
+            new Syntax(
+                    BUNDLE_LICENSE,
+                    "The Bundle-License header provides an optional machine readable form of license information. The purpose of this header is to automate some of the license processing required by many organizations",
+                    "Bundle License: http://www.opensource.org/licenses/jabberpl.php",
+                    "http://www.apache.org/licenses/LICENSE-2.0,<<EXTERNAL>>",
+                    Pattern.compile("(" + Verifier.URLPATTERN
+                            + "|<<EXTERNAL>>)"), new Syntax(
+                            DESCRIPTION_ATTRIBUTE,
+                            "Human readable description of the license",
+                            "description=\"Described the license here\"", null,
+                            Verifier.ANYPATTERN), new Syntax(LINK_ATTRIBUTE,
+                            "", "", null, Verifier.URLPATTERN)),
+            new Syntax(
+                    BUNDLE_LOCALIZATION,
+                    "The Bundle-Localization header contains the location in the bundle where localization files can be found. The default value is OSGI-INF/l10n/bundle. Translations are by default therefore OSGI-INF/l10n/bundle_de.properties, OSGI-INF/l10n/bundle_nl.properties, etc.",
+                    "Bundle-Localization: OSGI-INF/l10n/bundle",
+                    "OSGI-INF/l10n/bundle", Verifier.URLPATTERN),
+            new Syntax(
+                    BUNDLE_MANIFESTVERSION,
+                    "This header is set by bnd automatically to 2. The Bundle-ManifestVersion header defines that the bundle follows the rules of this specification. The Bundle-ManifestVersion header determines whether the bundle follows the rules of this specification.",
+                    "# Bundle-ManifestVersion: 2", "2", Verifier.NUMBERPATTERN),
+            new Syntax(
+                    BUNDLE_NAME,
+                    "This header will be derived from the  Bundle-SymbolicName if not set. The Bundle-Name header defines a readable name for this bundle. This should be a short, human-readable name that can contain spaces.",
+                    "Bundle-Name: My Bundle", null, Verifier.ANYPATTERN),
+            new Syntax(
+                    BUNDLE_NATIVECODE,
+                    "The Bundle-NativeCode header contains a specification of native code libraries contained in this bundle. ",
+                    "Bundle-NativeCode: /lib/http.DLL; osname = QNX; osversion = 3.1",
+                    null,
+                    Verifier.PATHPATTERN,
+                    new Syntax(OSNAME_ATTRIBUTE,
+                            "The name of the operating system", "osname=MacOS",
+                            Processor.join(Verifier.OSNAMES, ","),
+                            Verifier.ANYPATTERN),
+                    new Syntax(OSVERSION_ATTRIBUTE, "Operating System Version",
+                            "osversion=3.1", null, Verifier.ANYPATTERN),
+                    new Syntax(LANGUAGE_ATTRIBUTE, "Language ISO 639 code",
+                            "language=nl", null, Verifier.ISO639),
+                    new Syntax(PROCESSOR_ATTRIBUTE, "Processor name",
+                            "processor=x86", Processor.join(
+                                    Verifier.PROCESSORNAMES, ","),
+                            Verifier.ANYPATTERN),
+                    new Syntax(
+                            SELECTION_FILTER_ATTRIBUTE,
+                            "The value of this attribute must be a filter expression that indicates if the native code clause should be selected or not.",
+                            "selection-filter=\"(com.acme.windowing=win32)\"",
+                            null, Verifier.FILTERPATTERN)),
+            new Syntax(
+                    BUNDLE_REQUIREDEXECUTIONENVIRONMENT,
+                    "The Bundle-RequiredExecutionEnvironment contains a comma-separated list of execution environments that must be present on the Service Platform.",
+                    "Bundle-RequiredExecutionEnvironment: CDC-1.0/Foundation-1.0",
+                    Processor.join(Verifier.EES, ","), Verifier.ANYPATTERN),
+
+            new Syntax(
+                    BUNDLE_SYMBOLICNAME,
+                    "The Bundle-SymbolicName header specifies a non-localizable name for this bundle. The bundle symbolic name together with a version must identify a  unique bundle. The bundle symbolic name should be based on the reverse  domain name convention",
+                    "Bundle-SymbolicName: com.acme.foo.daffy;singleton:=true",
+                    "${p}",
+                    Verifier.SYMBOLICNAME,
+                    new Syntax(
+                            SINGLETON_DIRECTIVE,
+                            " Indicates that the bundle can only have  a single version resolved.  A value of true indicates that the bundle is a singleton bundle. The default value is false. The Framework must resolve at most one  bundle when multiple versions of a singleton bundle with the same symbolic name are installed. Singleton bundles do not affect the resolution of non-singleton bundles with the same symbolic name.",
+                            "false", "true,false", Verifier.TRUEORFALSEPATTERN),
+                    new Syntax(
+                            FRAGMENT_ATTACHMENT_DIRECTIVE,
+                            "Defines how fragments are allowed to be attached, see the fragments in Fragment Bundles on page73. The following values are valid for this directive:",
+                            "", "always|never|resolve-time", Pattern
+                                    .compile("always|never|resolve-time")),
+                    new Syntax(BLUEPRINT_WAIT_FOR_DEPENDENCIES_ATTRIBUTE, "",
+                            "", "true,false", Verifier.TRUEORFALSEPATTERN),
+                    new Syntax(BLUEPRINT_TIMEOUT_ATTRIBUTE, "", "",
+                            "30000,60000,300000", Verifier.NUMBERPATTERN)),
+
+            new Syntax(
+                    BUNDLE_UPDATELOCATION,
+                    "The Bundle-UpdateLocation header specifies a URL where an update for this bundle should come from. If the bundle is updated, this location should be used, if present, to retrieve the updated JAR file.",
+                    "Bundle-UpdateLocation: http://www.acme.com/Firewall/bundle.jar",
+                    null, Verifier.URLPATTERN),
+
+            new Syntax(
+                    BUNDLE_VENDOR,
+                    "The Bundle-Vendor header contains a human-readable description of the bundle vendor. ",
+                    "Bundle-Vendor: OSGi Alliance ", null, null),
+
+            new Syntax(
+                    BUNDLE_VERSION,
+                    "The Bundle-Version header specifies the version of this bundle",
+                    "Bundle-Version: 1.23.4.build200903221000", null,
+                    Verifier.VERSION),
+
+            new Syntax(
+                    DYNAMICIMPORT_PACKAGE,
+                    "The DynamicImport-Package header contains a comma-separated list of package names that should be dynamically imported when needed.",
+                    "DynamicImport-Package: com.acme.plugin.*", "",
+                    Verifier.WILDCARDNAMEPATTERN, version,
+                    bundle_symbolic_name, bundle_version),
+
+            new Syntax(
+                    EXPORT_PACKAGE,
+                    "The Export-Package header contains a declaration of exported packages.",
+                    "Export-Package: org.osgi.util.tracker;version=1.3",
+                    "${packages}",
+                    null,
+                    new Syntax(
+                            NO_IMPORT_DIRECTIVE,
+                            "By default, bnd makes all exports also imports. Adding a -noimport to an exported package will make it export only",
+                            "-noimport:=true", "true,false",
+                            Verifier.TRUEORFALSEPATTERN),
+                    new Syntax(
+                            USES_DIRECTIVE,
+                            "Calculated by bnd: It is a comma-separated list of package names that are used by the exported package",
+                            "Is calculated by bnd", null, null),
+                    new Syntax(
+                            MANDATORY_DIRECTIVE,
+                            "A comma-separated list of attribute names. Note that the use of a comma in the value requires it to be enclosed in double quotes. A bundle importing the package must specify the mandatory attributes, with a value that matches, to resolve to the exported package",
+                            "mandatory=\"bar,foo\"", null, null),
+                    new Syntax(
+                            INCLUDE_DIRECTIVE,
+                            "A comma-separated list of class names that must be visible to an importer",
+                            "include:=\"Qux*\"", null, null),
+                    new Syntax(
+                            EXCLUDE_DIRECTIVE,
+                            "A comma-separated list of class names that must not be visible to an importer",
+                            "exclude:=\"QuxImpl*,BarImpl\"", null,
+                            Verifier.WILDCARDNAMEPATTERN), new Syntax(
+                            IMPORT_DIRECTIVE, "Experimental", "", null, null)
+
+            ),
+            new Syntax(EXPORT_SERVICE, "Deprecated",
+                    "Export-Service: org.osgi.service.log.LogService ",
+                    "${classes;implementing;*}", null),
+            new Syntax(
+                    FRAGMENT_HOST,
+                    "The Fragment-Host header defines the host bundle for this fragment.",
+                    "Fragment-Host: org.eclipse.swt; bundle-version=\"[3.0.0,4.0.0)\"",
+                    null,
+                    null,
+                    new Syntax(
+                            EXTENSION_DIRECTIVE,
+                            " Indicates this extension is a system or boot class path extension. It is only applicable when the Fragment-Host is the System Bundle",
+                            "extension:=framework", "framework,bootclasspath",
+                            Pattern.compile("framework|bootclasspath")),
+                    bundle_version),
+            new Syntax(
+                    IMPORT_PACKAGE,
+                    "This header is normally calculated by bnd, however, you can decorate packages or skip packages. The Import-Package header declares the imported packages for this bundle",
+                    "Import-Package: !com.exotic.*, com.acme.foo;vendor=ACME, *",
+                    "${exported_packages}",
+                    Verifier.WILDCARDNAMEPATTERN,
+                    new Syntax(
+                            REMOVE_ATTRIBUTE_DIRECTIVE,
+                            "Remove the given attributes from matching imported packages",
+                            "-remove-attribute:=foo.*", null,
+                            Verifier.WILDCARDNAMEPATTERN),
+                    new Syntax(
+                            RESOLUTION_DIRECTIVE,
+                            "Indicates that the packages must be resolved if the value is mandatory, which is the default. If mandatory packages cannot be resolved, then the bundle must fail to resolve. A value of optional indicates that the packages are optional",
+                            "resolution:=optional", "mandatory,optional",
+                            Pattern.compile("mandatory|optional")
+
+                    ), version, bundle_symbolic_name, bundle_version),
+
+            new Syntax(
+                    REQUIRE_BUNDLE,
+                    "The Require-Bundle header specifies the required exports from another bundle.",
+                    "Require-Bundle: com.acme.chess",
+                    null,
+                    Verifier.WILDCARDNAMEPATTERN,
+
+                    new Syntax(
+                            VISIBILITY_DIRECTIVE,
+                            " If the value is private (Default), then all visible packages from the required bundles are not re-exported. If the value is reexport then bundles that require this bundle will transitively have access to these required bundle’s exported packages.",
+                            "visibility:=private", "private,reexport", Pattern
+                                    .compile("private|reexport")),
+
+                    new Syntax(
+                            RESOLUTION_DIRECTIVE,
+                            "If the value is mandatory (default) then the required bundle must exist for this bundle to resolve. If the value is optional, the bundle will resolve even if the required bundle does not exist.",
+                            "resolution:=optional", "mandatory,optional",
+                            Pattern.compile("mandatory|optional")),
+
+                    new Syntax(
+                            SPLIT_PACKAGE_DIRECTIVE,
+                            "Indicates how an imported package should be merged when it is split between different exporters. The default is merge-first with warning",
+                            "-split-package:=merge-first",
+                            "merge-first,merge-last,error,first",
+                            Pattern
+                                    .compile("merge-first|merge-last|error|first")),
+                    bundle_version
+
+            ),
+            new Syntax(
+                    BUILDPATH,
+                    "Provides the class path for building the jar. The entries are references to the repository",
+                    "-buildpath=osgi;version=4.1", "${repo;bsns}",
+                    Verifier.SYMBOLICNAME, path_version),
+            new Syntax(
+                    BUMPPOLICY,
+                    "Sets the version bump policy. This is a parameter to the ${version} macro.",
+                    "-bumppolicy==+0", "==+,=+0,+00", Pattern
+                            .compile("[=+-0][=+-0][=+-0]")),
+
+            new Syntax(
+                    CONDUIT,
+                    "Allows a bnd file to point to files which will be returned when the bnd file is build",
+                    "-conduit= jar/osgi.jar", null, null),
+
+            new Syntax(
+                    DEPENDSON,
+                    "List of project names that this project directly depends on. These projects are always build ahead of this project",
+                    "-dependson=org.acme.cm", "${projects}", null),
+
+            new Syntax(DEPLOYREPO,
+                    "Specifies to which repo the project should be deployed.",
+                    "-deployrepo=cnf", "${repos}", null),
+
+            new Syntax(
+                    DONOTCOPY,
+                    "Regular expression for names of files and directories that should not be copied when discovered",
+                    "-donotcopy=(CVS|\\.svn)", null, null),
+
+            new Syntax(
+                    EXPORT_CONTENTS,
+                    "Build the JAR in the normal way but use this header for the Export-Package header manifest generation, same format",
+                    "-exportcontents=!*impl*,*;version=3.0", null, null),
+
+            new Syntax(
+                    FAIL_OK,
+                    "Return with an ok status (0) even if the build generates errors",
+                    "-failok=true", "true,false", Verifier.TRUEORFALSEPATTERN),
+
+            new Syntax(
+                    INCLUDE,
+                    "Include files. If an entry starts with '-', it does not have to exist. If it starts with '~', it must not overwrite any existing properties",
+                    "-include: -${java.user}/.bnd", null, null),
+
+            new Syntax(
+                    INCLUDERESOURCE,
+                    "Include resources from the file system. You can specify a directory, or file. All files are copied to the root, unless a destination directory is indicated",
+                    "-includeresource: lib=jar", null, null),
+
+            new Syntax(
+                    MAKE,
+                    "Set patterns for make plugins. These patterns are used to find a plugin that can make a resource that can not be found.",
+                    "-make: (*).jar;type=bnd;  recipe=\"bnd/$1.bnd\"", null,
+                    null, new Syntax("type", "Type name for plugin",
+                            "type=bnd", "bnd", null), new Syntax("recipe",
+                            "Recipe for the plugin, can use back references",
+                            "recipe=\"bnd/$1.bnd\"", "bnd", null)),
+
+            new Syntax(
+                    MANIFEST,
+                    "Directly include a manifest, do not use the calculated manifest",
+                    "-manifest = META-INF/MANIFEST.MF", null, null),
+
+            new Syntax(NOEXTRAHEADERS, "Do not generate housekeeping headers",
+                    "-noextraheaders", "true,false",
+                    Verifier.TRUEORFALSEPATTERN),
+
+            new Syntax(NOUSES,
+                    "Do not calculate the uses: directive on exports",
+                    "-nouses=true", "true,false", Verifier.TRUEORFALSEPATTERN),
+
+            new Syntax(NOPE,
+                    "Deprecated, use -nobundles. ",
+                    "-nope=true", "true,false", Verifier.TRUEORFALSEPATTERN),
+
+            new Syntax(
+                    PEDANTIC,
+                    "Warn about things that are not really wrong but still not right",
+                    "-nope=true", "true,false", Verifier.TRUEORFALSEPATTERN),
+
+            new Syntax(
+                    PLUGIN,
+                    "Define the plugins",
+                    "-plugin=aQute.lib.spring.SpringComponent,aQute.lib.deployer.FileRepo;location=${repo}",
+                    null, null),
+
+            new Syntax(SERVICE_COMPONENT,
+                    "The header for Declarative Services",
+                    "Service-Component=com.acme.Foo?;activate='start'", null,
+                    null),
+
+            new Syntax(POM, "Generate a maven pom", "-pom=true", "true,false",
+                    Verifier.TRUEORFALSEPATTERN),
+
+            new Syntax(RELEASEREPO,
+                    "Specifies to which repo the project should be released.",
+                    "-releaserepo=cnf", "${repos}", null),
+
+            new Syntax(REMOVEHEADERS,
+                    "Remove all headers that match the regular expressions",
+                    "-removeheaders=FOO_.*,Proprietary", null, null),
+            new Syntax(
+                    RESOURCEONLY,
+                    "Normally bnd warns when the JAR does not contain any classes, this option suppresses this warning",
+                    "-resourceonly=true", "true,false",
+                    Verifier.TRUEORFALSEPATTERN),
+            new Syntax(SOURCES, "Include sources in the jar", "-sources=true",
+                    "true,false", Verifier.TRUEORFALSEPATTERN),
+            new Syntax(
+                    SOURCEPATH,
+                    "List of directory names that used to source sources for -sources",
+                    "-sourcepath:= src, test", null, null),
+            new Syntax(
+                    SUB,
+                    "Build a set of bnd files that use this bnd file as a basis. The list of bnd file can be specified with wildcards",
+                    "-sub=com.acme.*.bnd", null, null),
+            new Syntax(
+                    RUNPROPERTIES,
+                    "Properties that are set as system properties before the framework is started",
+                    "-runproperties= foo=3, bar=4", null, null),
+            new Syntax(RUNSYSTEMPACKAGES,
+                    "Add additional system packages to a framework run",
+                    "-runsystempackages=com.acme.foo,javax.management", null,
+                    null),
+            new Syntax(
+                    RUNBUNDLES,
+                    "Add additional bundles, specified with their bsn and version like in -buildpath, that are started before the project is run",
+                    "-runbundles=osgi;version=\"[4.1,4.2)\", junit.junit, com.acme.foo;version=project",
+                    null, Verifier.SYMBOLICNAME, path_version),
+            new Syntax(
+                    RUNPATH,
+                    "Additional JARs for the VM path, should include the framework",
+                    "-runpath=org.eclipse.osgi;version=3.5", null, null,
+                    path_version),
+            new Syntax(
+                    RUNVM,
+                    "Additional arguments for the VM invokation. Keys that start with a - are added as options, otherwise they are treated as -D properties for the VM",
+                    "-runvm=-Xmax=30", null, null),
+            new Syntax(
+                    VERSIONPOLICY,
+                    "Provides a version policy to imports that are calculated from exports",
+                    "-versionpolicy = \"[${version;==;${@}},${version;+;${@}})\"",
+                    null, null)
+
+                                                                 };
+
+    public final static Map<String, Syntax> HELP                 = new HashMap<String, Syntax>();
+
+    static {
+        for (Syntax s : syntaxes) {
+            HELP.put(s.header, s);
+        }
+    }
+
+    public Syntax(String header, String lead, String example, String values,
+            Pattern pattern, Syntax... children) {
+        this.header = header;
+        this.children = children;
+        this.lead = lead;
+        this.example = example;
+        this.values = values;
+        this.pattern = pattern;
+    }
+
+    public String getLead() {
+        return lead;
+    }
+
+    public String getExample() {
+        return example;
+    }
+
+    public String getValues() {
+        return values;
+    }
+
+    public String getPattern() {
+        return lead;
+    }
+
+    public Syntax[] getChildren() {
+        return children;
+    }
+
+    public String getHeader() {
+        return header;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/help/Warnings.java b/bundleplugin/src/main/java/aQute/bnd/help/Warnings.java
new file mode 100644
index 0000000..f2dddc4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/help/Warnings.java
@@ -0,0 +1,5 @@
+package aQute.bnd.help;
+
+public interface Warnings {
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/help/packageinfo b/bundleplugin/src/main/java/aQute/bnd/help/packageinfo
new file mode 100644
index 0000000..a4f1546
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/help/packageinfo
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/Make.java b/bundleplugin/src/main/java/aQute/bnd/make/Make.java
new file mode 100644
index 0000000..6827c6d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/Make.java
@@ -0,0 +1,99 @@
+package aQute.bnd.make;
+
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class Make {
+    Builder                               builder;
+    Map<Instruction, Map<String, String>> make;
+
+    public Make(Builder builder) {
+        this.builder = builder;
+    }
+
+    public Resource process(String source) {
+        Map<Instruction, Map<String, String>> make = getMakeHeader();
+        builder.trace("make " + source);
+
+        for (Map.Entry<Instruction, Map<String, String>> entry : make
+                .entrySet()) {
+            Instruction instr = entry.getKey();
+            Matcher m = instr.getMatcher(source);
+            if (m.matches() || instr.isNegated()) {
+                Map<String, String> arguments = replace(m, entry.getValue());
+                List<MakePlugin> plugins = builder.getPlugins(MakePlugin.class);
+                for (MakePlugin plugin : plugins) {
+                    try {
+                        Resource resource = plugin.make(builder,
+                                source, arguments);
+                        if (resource != null) {
+                            builder.trace("Made " + source + " from args "
+                                    + arguments + " with " + plugin);
+                            return resource;
+                        }
+                    } catch (Exception e) {
+                        builder.error("Plugin " + plugin
+                                + " generates error when use in making "
+                                + source + " with args " + arguments, e);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private Map<String, String> replace(Matcher m, Map<String, String> value) {
+        Map<String, String> newArgs = Processor.newMap();
+        for (Map.Entry<String, String> entry : value.entrySet()) {
+            String s = entry.getValue();
+            s = replace(m, s);
+            newArgs.put(entry.getKey(), s);
+        }
+        return newArgs;
+    }
+
+    String replace(Matcher m, CharSequence s) {
+        StringBuffer sb = new StringBuffer();
+        int max = '0' + m.groupCount() + 1;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '$' && i < s.length() - 1) {
+                c = s.charAt(++i);
+                if (c >= '0' && c <= max) {
+                    int index = c - '0';
+                    String replacement = m.group(index);
+                    if (replacement != null)
+                        sb.append(replacement);
+                } else {
+                    if (c == '$')
+                        i++;
+                    sb.append(c);
+                }
+            } else
+                sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    Map<Instruction, Map<String, String>> getMakeHeader() {
+        if (make != null)
+            return make;
+        make = Processor.newMap();
+
+        String s = builder.getProperty(Builder.MAKE);
+        Map<String, Map<String, String>> make = builder.parseHeader(s);
+        for (Iterator<Map.Entry<String, Map<String, String>>> e = make
+                .entrySet().iterator(); e.hasNext();) {
+            Map.Entry<String, Map<String, String>> entry = e.next();
+            String pattern = Processor.removeDuplicateMarker(entry.getKey());
+            
+            Instruction instr = Instruction.getPattern(pattern);
+            this.make.put(instr, entry.getValue());
+        }
+
+        return this.make;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java b/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java
new file mode 100644
index 0000000..f2e90d8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java
@@ -0,0 +1,65 @@
+package aQute.bnd.make;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.build.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class MakeBnd implements MakePlugin, Constants {
+    final static Pattern JARFILE = Pattern.compile("(.+)\\.(jar|ipa)");
+
+    public Resource make(Builder builder, String destination,
+            Map<String, String> argumentsOnMake) throws Exception {
+        String type = (String) argumentsOnMake.get("type");
+        if (!"bnd".equals(type))
+            return null;
+
+        String recipe = (String) argumentsOnMake.get("recipe");
+        if (recipe == null) {
+            builder.error("No recipe specified on a make instruction for "
+                    + destination);
+            return null;
+        }
+        File bndfile = builder.getFile(recipe);
+        if (bndfile.isFile()) {
+            // We do not use a parent because then we would
+            // build ourselves again. So we can not blindly
+            // inherit the properties.
+            Builder bchild = builder.getSubBuilder();
+            bchild.removeBundleSpecificHeaders();
+            
+            // We must make sure that we do not include ourselves again!
+            bchild.setProperty(Analyzer.INCLUDE_RESOURCE, "");
+            bchild.setProperty(Analyzer.INCLUDERESOURCE, "");
+            bchild.setProperties(bndfile, builder.getBase());
+            
+            Jar jar = bchild.build();
+            Jar dot = builder.getTarget();
+
+            if (builder.hasSources()) {
+                for (String key : jar.getResources().keySet()) {
+                    if (key.startsWith("OSGI-OPT/src"))
+                        dot.putResource(key, (Resource) jar.getResource(key));
+                }
+            }
+            builder.getInfo(bchild, bndfile.getName() +": ");
+            String debug = bchild.getProperty(DEBUG);
+            if (Processor.isTrue(debug)) {
+                if ( builder instanceof ProjectBuilder ) {
+                    ProjectBuilder pb = (ProjectBuilder) builder;
+                    File target = pb.getProject().getTarget();
+                    String bsn = bchild.getBsn();
+                    File output = new File(target, bsn+".jar");
+                    jar.write(output);
+                    pb.getProject().getWorkspace().changedFile(output);
+                }
+            }
+            return new JarResource(jar);
+        } else
+            return null;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java b/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java
new file mode 100644
index 0000000..3d5e4c8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java
@@ -0,0 +1,45 @@
+package aQute.bnd.make;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class MakeCopy implements MakePlugin {
+
+    public Resource make(Builder builder, String destination,
+            Map<String, String> argumentsOnMake) throws Exception {
+        String type = argumentsOnMake.get("type");
+        if (!type.equals("copy"))
+            return null;
+
+        String from = argumentsOnMake.get("from");
+        if (from == null) {
+            String content = argumentsOnMake.get("content");
+            if (content == null)
+                throw new IllegalArgumentException(
+                        "No 'from' or 'content' field in copy "
+                                + argumentsOnMake);
+            return new EmbeddedResource(content.getBytes("UTF-8"),0);
+        } else {
+
+            File f = builder.getFile(from);
+            if (f.isFile())
+                return new FileResource(f);
+            else {
+                try {
+                    URL url = new URL(from);
+                    return new URLResource(url);
+                } catch(MalformedURLException mfue) {
+                    // We ignore this
+                }
+                throw new IllegalArgumentException(
+                        "Copy source does not exist " + from
+                                + " for destination " + destination);
+            }
+        }
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java b/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
new file mode 100644
index 0000000..2775a57
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
@@ -0,0 +1,165 @@
+package aQute.bnd.make.calltree;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+/**
+ * Create an XML call tree of a set of classes. The structure of the XML is:
+ * 
+ * <pre>
+ *    calltree ::= &lt;using&gt; &lt;usedby&gt;
+ *    using    ::= &lt;method&gt; *
+ *    usedby   ::= &lt;method&gt; *
+ *    method   ::= &lt;ref&gt; 
+ * </pre>
+ * 
+ * The <code>using</code> element contains methods in the set of classes and
+ * their references. The <code>usedby</code> element contains the used methods
+ * and their references to the set of classes. The <code>ref</code> element
+ * contains the class, the method name, the descriptor, and a pretty print
+ * version of the method.
+ * 
+ * The XML does not contain an XML processor instruction to make it easier to
+ * include in other XML. The encoding is always UTF-8.
+ * 
+ * This class can be used as a resource, just add it to a JAR and the data is
+ * generated when the resource is written (saving time when the JAR is up to
+ * date and does not have to be generated). However, the actual write method is
+ * a static method and can be called as well:
+ * {@link #writeCalltree(PrintWriter, Collection)}.
+ */
+public class CalltreeResource extends WriteResource {
+    Collection<Clazz> classes;
+
+    /**
+     * Create a resource for inclusion that will print a call tree.
+     * 
+     * @param values the classes for which the call tree is generated.
+     */
+    public CalltreeResource(Collection<Clazz> values) {
+        this.classes = values;
+        System.out.println(values);
+    }
+
+    /**
+     * We set the last modified to 0 so this resource does not force
+     * a new JAR if all other resources are up to date.
+     */
+    public long lastModified() {
+        return 0;
+    }
+
+    /**
+     * The write method is called to write the resource. We just call the static
+     * method.
+     */
+    public void write(OutputStream out) throws Exception {
+        OutputStreamWriter osw = new OutputStreamWriter(out, Constants.DEFAULT_CHARSET);
+        PrintWriter pw = new PrintWriter(osw);
+        try {
+            writeCalltree(pw, classes);
+        } finally {
+            pw.flush();
+        }
+    }
+
+    /**
+     * Print the call tree in XML.
+     * 
+     * @param out The output writer
+     * @param classes The set of classes
+     * @throws IOException Any errors
+     */
+    public static void writeCalltree(PrintWriter out, Collection<Clazz> classes)
+            throws Exception {
+
+        final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> using = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
+        final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> usedby = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
+
+        ClassDataCollector cd = new ClassDataCollector() {
+            Clazz.MethodDef source;
+
+            // Before a method is parsed
+            public void method(Clazz.MethodDef source) {
+                this.source = source;
+                xref(using, source, null);
+                xref(usedby, source, null);
+            }
+
+            // For any reference in the previous method.
+            public void reference(Clazz.MethodDef reference) {
+                xref(using, source, reference);
+                xref(usedby, reference, source);
+            }
+        };
+        for (Clazz clazz : classes) {
+            clazz.parseClassFileWithCollector(cd);
+        }
+
+        out.println("<calltree>");
+        xref(out, "using", using);
+        xref(out, "usedby", usedby);
+        out.println("</calltree>");
+    }
+
+    /*
+     * Add a new reference
+     */
+    private static void xref(
+            Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references,
+            Clazz.MethodDef source, Clazz.MethodDef reference) {
+        Set<Clazz.MethodDef> set = references.get(source);
+        if (set == null)
+            references.put(source, set=new TreeSet<Clazz.MethodDef>());
+        if ( reference != null)
+            set.add(reference);
+    }
+
+    /*
+     * Print out either using or usedby sets
+     */
+    private static void xref(PrintWriter out, String group,
+            Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references) {
+        out.println("  <" + group + ">");
+        for (Map.Entry<Clazz.MethodDef, Set<Clazz.MethodDef>> entry : references
+                .entrySet()) {
+            Clazz.MethodDef source = entry.getKey();
+            Set<Clazz.MethodDef> refs = entry.getValue();
+            method(out, "method", source, ">");
+            for (Clazz.MethodDef ref : refs) {
+                method(out, "ref", ref, "/>");
+            }
+            out.println("      </method>");
+        }
+        out.println("  </" + group + ">");
+    }
+
+    /*
+     * Print out a method.
+     */
+    private static void method(PrintWriter out, String element,
+            Clazz.MethodDef source, String closeElement) {
+        out.println("      <" + element + " class='" + source.clazz + "'"
+                + getAccess(source.access) + 
+                ( source.isConstructor() ? "" :  " name='" + source.name + "'") + " descriptor='" + source.descriptor + "' pretty='"
+                + source.getPretty() + "'" + closeElement);
+    }
+
+    private static String getAccess(int access) {
+        StringBuilder sb = new StringBuilder();
+        if ( Modifier.isPublic(access) )
+            sb.append(" public='true'");
+        if ( Modifier.isStatic(access) )
+            sb.append(" static='true'");
+        if ( Modifier.isProtected(access) )
+            sb.append(" protected='true'");
+        if ( Modifier.isInterface(access) )
+            sb.append(" interface='true'");
+        
+        return sb.toString();
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/component/ComponentAnnotationReader.java b/bundleplugin/src/main/java/aQute/bnd/make/component/ComponentAnnotationReader.java
new file mode 100644
index 0000000..a73e714
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/component/ComponentAnnotationReader.java
@@ -0,0 +1,343 @@
+package aQute.bnd.make.component;
+
+import static aQute.bnd.make.component.ServiceComponent.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.component.*;
+import aQute.lib.osgi.*;
+import aQute.libg.reporter.*;
+
+public class ComponentAnnotationReader extends ClassDataCollector {
+	String						EMPTY[]					= new String[0];
+	private static final String	V1_1					= "1.1.0";																																// "1.1.0"
+	static Pattern				BINDDESCRIPTOR			= Pattern
+																.compile("\\(L([^;]*);(Ljava/util/Map;|Lorg/osgi/framework/ServiceReference;)*\\)V");
+	static Pattern				BINDMETHOD				= Pattern.compile("(set|bind|add)(.)(.*)");
+
+	static Pattern				ACTIVATEDESCRIPTOR		= Pattern
+																.compile("\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)V");
+	static Pattern				OLDACTIVATEDESCRIPTOR	= Pattern
+																.compile("\\(Lorg/osgi/service/component/ComponentContext;\\)V");
+	static Pattern				OLDBINDDESCRIPTOR		= Pattern.compile("\\(L([^;]*);\\)V");
+	static Pattern				REFERENCEBINDDESCRIPTOR	= Pattern
+																.compile("\\(Lorg/osgi/framework/ServiceReference;\\)V");
+
+	Reporter					reporter				= new Processor();
+	String						method;
+	String						methodDescriptor;
+	int							methodAccess;
+	String						className;
+	Clazz						clazz;
+	String						interfaces[];
+	Set<String>					multiple				= new HashSet<String>();
+	Set<String>					optional				= new HashSet<String>();
+	Set<String>					dynamic					= new HashSet<String>();
+
+	Map<String, String>			map						= new TreeMap<String, String>();
+	Set<String>					descriptors				= new HashSet<String>();
+	List<String>				properties				= new ArrayList<String>();
+	String						version					= null;
+
+	// TODO make patterns for descriptors
+
+	ComponentAnnotationReader(Clazz clazz) {
+		this.clazz = clazz;
+	}
+
+	public void setReporter(Reporter reporter) {
+		this.reporter = reporter;
+	}
+
+	public Reporter getReporter() {
+		return this.reporter;
+	}
+
+	public static Map<String, String> getDefinition(Clazz c) throws Exception {
+		return getDefinition(c, new Processor());
+	}
+
+	public static Map<String, String> getDefinition(Clazz c, Reporter reporter) throws Exception {
+		ComponentAnnotationReader r = new ComponentAnnotationReader(c);
+		r.setReporter(reporter);
+		c.parseClassFileWithCollector(r);
+		r.finish();
+		return r.map;
+	}
+
+	public void annotation(Annotation annotation) {
+
+		if (annotation.getName().equals(Component.RNAME)) {
+			set(COMPONENT_NAME, annotation.get(Component.NAME), "<>");
+			set(COMPONENT_FACTORY, annotation.get(Component.FACTORY), false);
+			setBoolean(COMPONENT_ENABLED, annotation.get(Component.ENABLED), true);
+			setBoolean(COMPONENT_IMMEDIATE, annotation.get(Component.IMMEDIATE), false);
+			setBoolean(COMPONENT_SERVICEFACTORY, annotation.get(Component.SERVICEFACTORY), false);
+
+			if (annotation.get(Component.DESIGNATE) != null) {
+				String configs = annotation.get(Component.DESIGNATE);
+				if (configs != null) {
+					set(COMPONENT_DESIGNATE, Clazz.objectDescriptorToFQN(configs), "");
+				}
+			}
+
+			if (annotation.get(Component.DESIGNATE_FACTORY) != null) {
+				String configs = annotation.get(Component.DESIGNATE_FACTORY);
+				if (configs != null) {
+					set(COMPONENT_DESIGNATEFACTORY, Clazz.objectDescriptorToFQN(configs), "");
+				}
+			}
+
+			setVersion((String) annotation.get(Component.VERSION));
+
+			String configurationPolicy = annotation.get(Component.CONFIGURATION_POLICY);
+			if (configurationPolicy != null)
+				set(COMPONENT_CONFIGURATION_POLICY, configurationPolicy.toLowerCase(), "");
+
+			doProperties(annotation);
+
+			Object[] provides = (Object[]) annotation.get(Component.PROVIDE);
+			String[] p;
+			if (provides == null) {
+				// Use the found interfaces, but convert from internal to
+				// fqn.
+				if (interfaces != null) {
+					List<String> result = new ArrayList<String>();
+					for (int i = 0; i < interfaces.length; i++) {
+						if (!interfaces[i].equals("scala/ScalaObject") )
+							result.add(Clazz.internalToFqn(interfaces[i]));
+					}
+					p = result.toArray(EMPTY);
+				} else
+					p = EMPTY;
+			} else {
+				// We have explicit interfaces set
+				p = new String[provides.length];
+				for (int i = 0; i < provides.length; i++) {
+					p[i] = descriptorToFQN(provides[i].toString());
+				}
+			}
+			if (p.length > 0) {
+				set(COMPONENT_PROVIDE, Processor.join(Arrays.asList(p)), "<>");
+			}
+
+		} else if (annotation.getName().equals(Activate.RNAME)) {
+			if (!checkMethod())
+				setVersion(V1_1);
+
+			if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
+				reporter.error(
+						"Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+						className, methodDescriptor);
+
+			if (method.equals("activate")
+					&& OLDACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches()) {
+				// this is the default!
+			} else {
+				setVersion(V1_1);
+				set(COMPONENT_ACTIVATE, method, "<>");
+			}
+
+		} else if (annotation.getName().equals(Deactivate.RNAME)) {
+			if (!checkMethod())
+				setVersion(V1_1);
+
+			if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
+				reporter.error(
+						"Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+						className, methodDescriptor);
+			if (method.equals("deactivate")
+					&& OLDACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches()) {
+				// This is the default!
+			} else {
+				setVersion(V1_1);
+				set(COMPONENT_DEACTIVATE, method, "<>");
+			}
+		} else if (annotation.getName().equals(Modified.RNAME)) {
+			set(COMPONENT_MODIFIED, method, "<>");
+			setVersion(V1_1);
+		} else if (annotation.getName().equals(Reference.RNAME)) {
+
+			String name = (String) annotation.get(Reference.NAME);
+			String bind = method;
+			String unbind = null;
+
+			if (name == null) {
+				Matcher m = BINDMETHOD.matcher(method);
+				if (m.matches()) {
+					name = m.group(2).toLowerCase() + m.group(3);
+				} else {
+					name = method.toLowerCase();
+				}
+			}
+			String simpleName = name;
+
+			unbind = annotation.get(Reference.UNBIND);
+
+			if (bind != null) {
+				name = name + "/" + bind;
+				if (unbind != null)
+					name = name + "/" + unbind;
+			}
+			String service = annotation.get(Reference.SERVICE);
+
+			if (service != null) {
+				service = Clazz.objectDescriptorToFQN(service);
+			} else {
+				// We have to find the type of the current method to
+				// link it to the referenced service.
+				Matcher m = BINDDESCRIPTOR.matcher(methodDescriptor);
+				if (m.matches()) {
+					service = Clazz.internalToFqn(m.group(1));
+				} else
+					throw new IllegalArgumentException(
+							"Cannot detect the type of a Component Reference from the descriptor: "
+									+ methodDescriptor);
+			}
+
+			// Check if we have a target, this must be a filter
+			String target = annotation.get(Reference.TARGET);
+			if (target != null) {
+				Verifier.verifyFilter(target, 0);
+				service = service + target;
+			}
+
+			Integer c = annotation.get(Reference.TYPE);
+			if (c != null && !c.equals(0) && !c.equals((int) '1')) {
+				service = service + (char) c.intValue();
+			}
+
+			if (map.containsKey(name))
+				reporter.error(
+						"In component %s, Multiple references with the same name: %s. Previous def: %s, this def: %s",
+						name, map.get(name), service, "");
+			map.put(name, service);
+
+			if (isTrue(annotation.get(Reference.MULTIPLE)))
+				multiple.add(simpleName);
+			if (isTrue(annotation.get(Reference.OPTIONAL)))
+				optional.add(simpleName);
+			if (isTrue(annotation.get(Reference.DYNAMIC)))
+				dynamic.add(simpleName);
+
+			if (!checkMethod())
+				setVersion(V1_1);
+			else if (REFERENCEBINDDESCRIPTOR.matcher(methodDescriptor).matches()
+					|| !OLDBINDDESCRIPTOR.matcher(methodDescriptor).matches())
+				setVersion(V1_1);
+		}
+	}
+
+	private void setVersion(String v) {
+		if (v == null)
+			return;
+
+		if (version == null)
+			version = v;
+		else if (v.compareTo(version) > 0) // we're safe to 9.9.9
+			version = v;
+	}
+
+	private boolean checkMethod() {
+		return Modifier.isPublic(methodAccess) || Modifier.isProtected(methodAccess);
+	}
+
+	static Pattern	PROPERTY_PATTERN	= Pattern.compile("[^=]+=.+");
+
+	private void doProperties(Annotation annotation) {
+		Object[] properties = annotation.get(Component.PROPERTIES);
+
+		if (properties != null) {
+			for (Object o : properties) {
+				String p = (String) o;
+				if (PROPERTY_PATTERN.matcher(p).matches())
+					this.properties.add(p);
+				else
+					throw new IllegalArgumentException("Malformed property '" + p + "' on: "
+							+ annotation.get(Component.NAME));
+			}
+		}
+	}
+
+	private boolean isTrue(Object object) {
+		if (object == null)
+			return false;
+		return (Boolean) object;
+	}
+
+	private void setBoolean(String string, Object object, boolean b) {
+		if (object == null)
+			object = b;
+
+		Boolean bb = (Boolean) object;
+		if (bb == b)
+			return;
+
+		map.put(string, bb.toString());
+	}
+
+	private void set(String string, Object object, Object deflt) {
+		if (object == null || object.equals(deflt))
+			return;
+
+		map.put(string, object.toString());
+	}
+
+	/**
+	 * Skip L and ; and replace / for . in an object descriptor.
+	 * 
+	 * A string like Lcom/acme/Foo; becomes com.acme.Foo
+	 * 
+	 * @param string
+	 * @return
+	 */
+
+	private String descriptorToFQN(String string) {
+		StringBuilder sb = new StringBuilder();
+		for (int i = 1; i < string.length() - 1; i++) {
+			char c = string.charAt(i);
+			if (c == '/')
+				c = '.';
+			sb.append(c);
+		}
+		return sb.toString();
+	}
+
+	@Override public void classBegin(int access, String name) {
+		className = name;
+	}
+
+	@Override public void implementsInterfaces(String[] interfaces) {
+		this.interfaces = interfaces;
+	}
+
+	@Override public void method(int access, String name, String descriptor) {
+		this.method = name;
+		this.methodDescriptor = descriptor;
+		this.methodAccess = access;
+		descriptors.add(method);
+	}
+
+	void set(String name, Collection<String> l) {
+		if (l.size() == 0)
+			return;
+
+		set(name, Processor.join(l), "<>");
+	}
+
+	public void finish() {
+		set(COMPONENT_MULTIPLE, multiple);
+		set(COMPONENT_DYNAMIC, dynamic);
+		set(COMPONENT_OPTIONAL, optional);
+		set(COMPONENT_IMPLEMENTATION, clazz.getFQN(), "<>");
+		set(COMPONENT_PROPERTIES, properties);
+		if (version != null) {
+			set(COMPONENT_VERSION, version, "<>");
+			reporter.trace("Component %s is v1.1", map);
+		}
+		set(COMPONENT_DESCRIPTORS, descriptors);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java b/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
new file mode 100644
index 0000000..652d367
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
@@ -0,0 +1,665 @@
+package aQute.bnd.make.component;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.component.*;
+import aQute.bnd.make.metatype.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+import aQute.libg.version.*;
+
+/**
+ * This class is an analyzer plugin. It looks at the properties and tries to
+ * find out if the Service-Component header contains the bnd shortut syntax. If
+ * not, the header is copied to the output, if it does, an XML file is created
+ * and added to the JAR and the header is modified appropriately.
+ */
+public class ServiceComponent implements AnalyzerPlugin {
+	public final static String			NAMESPACE_STEM					= "http://www.osgi.org/xmlns/scr";
+	public final static String			JIDENTIFIER						= "<<identifier>>";
+	public final static String			COMPONENT_NAME					= "name:";
+	public final static String			COMPONENT_FACTORY				= "factory:";
+	public final static String			COMPONENT_SERVICEFACTORY		= "servicefactory:";
+	public final static String			COMPONENT_IMMEDIATE				= "immediate:";
+	public final static String			COMPONENT_ENABLED				= "enabled:";
+	public final static String			COMPONENT_DYNAMIC				= "dynamic:";
+	public final static String			COMPONENT_MULTIPLE				= "multiple:";
+	public final static String			COMPONENT_PROVIDE				= "provide:";
+	public final static String			COMPONENT_OPTIONAL				= "optional:";
+	public final static String			COMPONENT_PROPERTIES			= "properties:";
+	public final static String			COMPONENT_IMPLEMENTATION		= "implementation:";
+	public final static String			COMPONENT_DESIGNATE				= "designate:";
+	public final static String			COMPONENT_DESIGNATEFACTORY		= "designateFactory:";
+	public final static String			COMPONENT_DESCRIPTORS			= ".descriptors:";
+
+	// v1.1.0
+	public final static String			COMPONENT_VERSION				= "version:";
+	public final static String			COMPONENT_CONFIGURATION_POLICY	= "configuration-policy:";
+	public final static String			COMPONENT_MODIFIED				= "modified:";
+	public final static String			COMPONENT_ACTIVATE				= "activate:";
+	public final static String			COMPONENT_DEACTIVATE			= "deactivate:";
+
+	final static Map<String, String>	EMPTY							= Collections.emptyMap();
+
+	public final static String[]		componentDirectives				= new String[] {
+			COMPONENT_FACTORY, COMPONENT_IMMEDIATE, COMPONENT_ENABLED, COMPONENT_DYNAMIC,
+			COMPONENT_MULTIPLE, COMPONENT_PROVIDE, COMPONENT_OPTIONAL, COMPONENT_PROPERTIES,
+			COMPONENT_IMPLEMENTATION, COMPONENT_SERVICEFACTORY, COMPONENT_VERSION,
+			COMPONENT_CONFIGURATION_POLICY, COMPONENT_MODIFIED, COMPONENT_ACTIVATE,
+			COMPONENT_DEACTIVATE, COMPONENT_NAME, COMPONENT_DESCRIPTORS, COMPONENT_DESIGNATE,
+			COMPONENT_DESIGNATEFACTORY									};
+
+	public final static Set<String>		SET_COMPONENT_DIRECTIVES		= new HashSet<String>(
+																				Arrays.asList(componentDirectives));
+
+	public final static Set<String>		SET_COMPONENT_DIRECTIVES_1_1	= //
+																		new HashSet<String>(
+																				Arrays.asList(
+																						COMPONENT_VERSION,
+																						COMPONENT_CONFIGURATION_POLICY,
+																						COMPONENT_MODIFIED,
+																						COMPONENT_ACTIVATE,
+																						COMPONENT_DEACTIVATE));
+
+	public boolean analyzeJar(Analyzer analyzer) throws Exception {
+
+		ComponentMaker m = new ComponentMaker(analyzer);
+
+		Map<String, Map<String, String>> l = m.doServiceComponent();
+
+		analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor.printClauses(l));
+
+		analyzer.getInfo(m, "Service-Component: ");
+		m.close();
+
+		return false;
+	}
+
+	private static class ComponentMaker extends Processor {
+		Analyzer	analyzer;
+
+		ComponentMaker(Analyzer analyzer) {
+			super(analyzer);
+			this.analyzer = analyzer;
+		}
+
+		/**
+		 * Iterate over the Service Component entries. There are two cases:
+		 * <ol>
+		 * <li>An XML file reference</li>
+		 * <li>A FQN/wildcard with a set of attributes</li>
+		 * </ol>
+		 * 
+		 * An XML reference is immediately expanded, an FQN/wildcard is more
+		 * complicated and is delegated to
+		 * {@link #componentEntry(Map, String, Map)}.
+		 * 
+		 * @throws Exception
+		 */
+		Map<String, Map<String, String>> doServiceComponent() throws Exception {
+			Map<String, Map<String, String>> serviceComponents = newMap();
+			String header = getProperty(SERVICE_COMPONENT);
+			Map<String, Map<String, String>> sc = parseHeader(header);
+
+			for (Map.Entry<String, Map<String, String>> entry : sc.entrySet()) {
+				String name = entry.getKey();
+				Map<String, String> info = entry.getValue();
+
+				try {
+					if (name.indexOf('/') >= 0 || name.endsWith(".xml")) {
+						// Normal service component, we do not process it
+						serviceComponents.put(name, EMPTY);
+					} else {
+						componentEntry(serviceComponents, name, info);
+					}
+				} catch (Exception e) {
+					e.printStackTrace();
+					error("Invalid Service-Component header: %s %s, throws %s", name, info, e);
+				}
+			}
+			return serviceComponents;
+		}
+
+		/**
+		 * Parse an entry in the Service-Component header. This header supports
+		 * the following types:
+		 * <ol>
+		 * <li>An FQN + attributes describing a component</li>
+		 * <li>A wildcard expression for finding annotated components.</li>
+		 * </ol>
+		 * The problem is the distinction between an FQN and a wildcard because
+		 * an FQN can also be used as a wildcard.
+		 * 
+		 * If the info specifies {@link Constants#NOANNOTATIONS} then wildcards
+		 * are an error and the component must be fully described by the info.
+		 * Otherwise the FQN/wildcard is expanded into a list of classes with
+		 * annotations. If this list is empty, the FQN case is interpreted as a
+		 * complete component definition. For the wildcard case, it is checked
+		 * if any matching classes for the wildcard have been compiled for a
+		 * class file format that does not support annotations, this can be a
+		 * problem with JSR14 who silently ignores annotations. An error is
+		 * reported in such a case.
+		 * 
+		 * @param serviceComponents
+		 * @param name
+		 * @param info
+		 * @throws Exception
+		 * @throws IOException
+		 */
+		private void componentEntry(Map<String, Map<String, String>> serviceComponents,
+				String name, Map<String, String> info) throws Exception, IOException {
+
+			boolean annotations = !Processor.isTrue(info.get(NOANNOTATIONS));
+			boolean fqn = Verifier.isFQN(name);
+
+			if (annotations) {
+
+				// Annotations possible!
+
+				Collection<Clazz> annotatedComponents = analyzer.getClasses("",
+						QUERY.ANNOTATION.toString(), Component.class.getName(), //
+						QUERY.NAMED.toString(), name //
+						);
+
+				if (fqn) {
+					if (annotatedComponents.isEmpty()) {
+
+						// No annotations, fully specified in header
+
+						createComponentResource(serviceComponents, name, info);
+					} else {
+
+						// We had a FQN so expect only one
+
+						for (Clazz c : annotatedComponents) {
+							annotated(serviceComponents, c, info);
+						}
+					}
+				} else {
+
+					// We did not have an FQN, so expect the use of wildcards
+
+					if (annotatedComponents.isEmpty())
+						checkAnnotationsFeasible(name);
+					else
+						for (Clazz c : annotatedComponents) {
+							annotated(serviceComponents, c, info);
+						}
+				}
+			} else {
+				// No annotations
+				if (fqn)
+					createComponentResource(serviceComponents, name, info);
+				else
+					error("Set to %s but entry %s is not an FQN ", NOANNOTATIONS, name);
+
+			}
+		}
+
+		/**
+		 * Check if annotations are actually feasible looking at the class
+		 * format. If the class format does not provide annotations then it is
+		 * no use specifying annotated components.
+		 * 
+		 * @param name
+		 * @return
+		 * @throws Exception
+		 */
+		private Collection<Clazz> checkAnnotationsFeasible(String name) throws Exception {
+			Collection<Clazz> not = analyzer.getClasses("", QUERY.NAMED.toString(), name //
+					);
+			
+			if (not.isEmpty())
+				if ( "*".equals(name))
+					return not;
+				else
+					error("Specified %s but could not find any class matching this pattern", name);
+
+			for (Clazz c : not) {
+				if (c.getFormat().hasAnnotations())
+					return not;
+			}
+			
+			warning("Wildcards are used (%s) requiring annotations to decide what is a component. Wildcard maps to classes that are compiled with java.target < 1.5. Annotations were introduced in Java 1.5",
+					name);
+			
+			return not;
+		}
+
+		void annotated(Map<String, Map<String, String>> components, Clazz c,
+				Map<String, String> info) throws Exception {
+			// Get the component definition
+			// from the annotations
+			Map<String, String> map = ComponentAnnotationReader.getDefinition(c, this);
+
+			// Pick the name, the annotation can override
+			// the name.
+			String localname = map.get(COMPONENT_NAME);
+			if (localname == null)
+				localname = c.getFQN();
+
+			// Override the component info without manifest
+			// entries. We merge the properties though.
+
+			String merged = Processor.merge(info.remove(COMPONENT_PROPERTIES),
+					map.remove(COMPONENT_PROPERTIES));
+			if (merged != null && merged.length() > 0)
+				map.put(COMPONENT_PROPERTIES, merged);
+			map.putAll(info);
+			createComponentResource(components, localname, map);
+		}
+
+		private void createComponentResource(Map<String, Map<String, String>> components,
+				String name, Map<String, String> info) throws IOException {
+
+			// We can override the name in the parameters
+			if (info.containsKey(COMPONENT_NAME))
+				name = info.get(COMPONENT_NAME);
+
+			// Assume the impl==name, but allow override
+			String impl = name;
+			if (info.containsKey(COMPONENT_IMPLEMENTATION))
+				impl = info.get(COMPONENT_IMPLEMENTATION);
+
+			// Check if such a class exists
+			analyzer.referTo(impl);
+
+			boolean designate = designate(name, info.get(COMPONENT_DESIGNATE), false)
+					|| designate(name, info.get(COMPONENT_DESIGNATEFACTORY), true);
+
+			// If we had a designate, we want a default configuration policy of
+			// require.
+			if (designate && info.get(COMPONENT_CONFIGURATION_POLICY) == null)
+				info.put(COMPONENT_CONFIGURATION_POLICY, "require");
+
+			// We have a definition, so make an XML resources
+			Resource resource = createComponentResource(name, impl, info);
+			analyzer.getJar().putResource("OSGI-INF/" + name + ".xml", resource);
+
+			components.put("OSGI-INF/" + name + ".xml", EMPTY);
+
+		}
+
+		/**
+		 * Create a Metatype and Designate record out of the given
+		 * configurations.
+		 * 
+		 * @param name
+		 * @param config
+		 */
+		private boolean designate(String name, String config, boolean factory) {
+			if (config == null)
+				return false;
+
+			for (String c : Processor.split(config)) {
+				Clazz clazz = analyzer.getClassspace().get(Clazz.fqnToPath(c));
+				if (clazz != null) {
+					analyzer.referTo(c);
+					MetaTypeReader r = new MetaTypeReader(clazz, analyzer);
+					r.setDesignate(name, factory);
+					String rname = "OSGI-INF/metatype/" + name + ".xml";
+
+					analyzer.getJar().putResource(rname, r);
+				} else {
+					analyzer.error(
+							"Cannot find designated configuration class %s for component %s", c,
+							name);
+				}
+			}
+			return true;
+		}
+
+		/**
+		 * Create the resource for a DS component.
+		 * 
+		 * @param list
+		 * @param name
+		 * @param info
+		 * @throws UnsupportedEncodingException
+		 */
+		Resource createComponentResource(String name, String impl, Map<String, String> info)
+				throws IOException {
+			String namespace = getNamespace(info);
+			ByteArrayOutputStream out = new ByteArrayOutputStream();
+			PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, Constants.DEFAULT_CHARSET));
+			pw.println("<?xml version='1.0' encoding='utf-8'?>");
+			if (namespace != null)
+				pw.print("<scr:component xmlns:scr='" + namespace + "'");
+			else
+				pw.print("<component");
+
+			doAttribute(pw, name, "name");
+			doAttribute(pw, info.get(COMPONENT_FACTORY), "factory");
+			doAttribute(pw, info.get(COMPONENT_IMMEDIATE), "immediate", "false", "true");
+			doAttribute(pw, info.get(COMPONENT_ENABLED), "enabled", "true", "false");
+			doAttribute(pw, info.get(COMPONENT_CONFIGURATION_POLICY), "configuration-policy",
+					"optional", "require", "ignore");
+			doAttribute(pw, info.get(COMPONENT_ACTIVATE), "activate", JIDENTIFIER);
+			doAttribute(pw, info.get(COMPONENT_DEACTIVATE), "deactivate", JIDENTIFIER);
+			doAttribute(pw, info.get(COMPONENT_MODIFIED), "modified", JIDENTIFIER);
+
+			pw.println(">");
+
+			// Allow override of the implementation when people
+			// want to choose their own name
+			pw.println("  <implementation class='" + (impl == null ? name : impl) + "'/>");
+
+			String provides = info.get(COMPONENT_PROVIDE);
+			boolean servicefactory = Processor.isTrue(info.get(COMPONENT_SERVICEFACTORY));
+
+			if (servicefactory && Processor.isTrue(info.get(COMPONENT_IMMEDIATE))) {
+				// TODO can become error() if it is up to me
+				warning("For a Service Component, the immediate option and the servicefactory option are mutually exclusive for %(%s)",
+						name, impl);
+			}
+			provide(pw, provides, servicefactory, impl);
+			properties(pw, info);
+			reference(info, pw);
+
+			if (namespace != null)
+				pw.println("</scr:component>");
+			else
+				pw.println("</component>");
+
+			pw.close();
+			byte[] data = out.toByteArray();
+			out.close();
+			return new EmbeddedResource(data, 0);
+		}
+
+		private void doAttribute(PrintWriter pw, String value, String name, String... matches) {
+			if (value != null) {
+				if (matches.length != 0) {
+					if (matches.length == 1 && matches[0].equals(JIDENTIFIER)) {
+						if (!Verifier.isIdentifier(value))
+							error("Component attribute %s has value %s but is not a Java identifier",
+									name, value);
+					} else {
+
+						if (!Verifier.isMember(value, matches))
+							error("Component attribute %s has value %s but is not a member of %s",
+									name, value, Arrays.toString(matches));
+					}
+				}
+				pw.print(" ");
+				pw.print(name);
+				pw.print("='");
+				pw.print(value);
+				pw.print("'");
+			}
+		}
+
+		/**
+		 * Check if we need to use the v1.1 namespace (or later).
+		 * 
+		 * @param info
+		 * @return
+		 */
+		private String getNamespace(Map<String, String> info) {
+			String version = info.get(COMPONENT_VERSION);
+			if (version != null) {
+				try {
+					Version v = new Version(version);
+					return NAMESPACE_STEM + "/v" + v;
+				} catch (Exception e) {
+					error("version: specified on component header but not a valid version: "
+							+ version);
+					return null;
+				}
+			}
+			for (String key : info.keySet()) {
+				if (SET_COMPONENT_DIRECTIVES_1_1.contains(key)) {
+					return NAMESPACE_STEM + "/v1.1.0";
+				}
+			}
+			return null;
+		}
+
+		/**
+		 * Print the Service-Component properties element
+		 * 
+		 * @param pw
+		 * @param info
+		 */
+		void properties(PrintWriter pw, Map<String, String> info) {
+			Collection<String> properties = split(info.get(COMPONENT_PROPERTIES));
+			for (Iterator<String> p = properties.iterator(); p.hasNext();) {
+				String clause = p.next();
+				int n = clause.indexOf('=');
+				if (n <= 0) {
+					error("Not a valid property in service component: " + clause);
+				} else {
+					String type = null;
+					String name = clause.substring(0, n);
+					if (name.indexOf('@') >= 0) {
+						String parts[] = name.split("@");
+						name = parts[1];
+						type = parts[0];
+					} else if (name.indexOf(':') >= 0) {
+						String parts[] = name.split(":");
+						name = parts[0];
+						type = parts[1];
+					}
+					String value = clause.substring(n + 1).trim();
+					// TODO verify validity of name and value.
+					pw.print("  <property name='");
+					pw.print(name);
+					pw.print("'");
+
+					if (type != null) {
+						if (VALID_PROPERTY_TYPES.matcher(type).matches()) {
+							pw.print(" type='");
+							pw.print(type);
+							pw.print("'");
+						} else {
+							warning("Invalid property type '" + type + "' for property " + name);
+						}
+					}
+
+					String parts[] = value.split("\\s*(\\||\\n)\\s*");
+					if (parts.length > 1) {
+						pw.println(">");
+						for (String part : parts) {
+							pw.println(part);
+						}
+						pw.println("</property>");
+					} else {
+						pw.print(" value='");
+						pw.print(parts[0]);
+						pw.println("'/>");
+					}
+				}
+			}
+		}
+
+		/**
+		 * @param pw
+		 * @param provides
+		 */
+		void provide(PrintWriter pw, String provides, boolean servicefactory, String impl) {
+			if (provides != null) {
+				if (!servicefactory)
+					pw.println("  <service>");
+				else
+					pw.println("  <service servicefactory='true'>");
+
+				StringTokenizer st = new StringTokenizer(provides, ",");
+				while (st.hasMoreTokens()) {
+					String interfaceName = st.nextToken();
+					pw.println("    <provide interface='" + interfaceName + "'/>");
+					analyzer.referTo(interfaceName);
+
+					// TODO verifies the impl. class extends or implements the
+					// interface
+				}
+				pw.println("  </service>");
+			} else if (servicefactory)
+				warning("The servicefactory:=true directive is set but no service is provided, ignoring it");
+		}
+
+		public final static Pattern	REFERENCE	= Pattern.compile("([^(]+)(\\(.+\\))?");
+
+		/**
+		 * @param info
+		 * @param pw
+		 */
+
+		void reference(Map<String, String> info, PrintWriter pw) {
+			Collection<String> dynamic = new ArrayList<String>(split(info.get(COMPONENT_DYNAMIC)));
+			Collection<String> optional = new ArrayList<String>(split(info.get(COMPONENT_OPTIONAL)));
+			Collection<String> multiple = new ArrayList<String>(split(info.get(COMPONENT_MULTIPLE)));
+
+			Collection<String> descriptors = split(info.get(COMPONENT_DESCRIPTORS));
+
+			for (Map.Entry<String, String> entry : info.entrySet()) {
+
+				// Skip directives
+				String referenceName = entry.getKey();
+				if (referenceName.endsWith(":")) {
+					if (!SET_COMPONENT_DIRECTIVES.contains(referenceName))
+						error("Unrecognized directive in Service-Component header: "
+								+ referenceName);
+					continue;
+				}
+
+				// Parse the bind/unbind methods from the name
+				// if set. They are separated by '/'
+				String bind = null;
+				String unbind = null;
+
+				boolean unbindCalculated = false;
+
+				if (referenceName.indexOf('/') >= 0) {
+					String parts[] = referenceName.split("/");
+					referenceName = parts[0];
+					bind = parts[1];
+					if (parts.length > 2) {
+						unbind = parts[2];
+					} else {
+						unbindCalculated = true;
+						if (bind.startsWith("add"))
+							unbind = bind.replaceAll("add(.+)", "remove$1");
+						else
+							unbind = "un" + bind;
+					}
+				} else if (Character.isLowerCase(referenceName.charAt(0))) {
+					unbindCalculated = true;
+					bind = "set" + Character.toUpperCase(referenceName.charAt(0))
+							+ referenceName.substring(1);
+					unbind = "un" + bind;
+				}
+
+				String interfaceName = entry.getValue();
+				if (interfaceName == null || interfaceName.length() == 0) {
+					error("Invalid Interface Name for references in Service Component: "
+							+ referenceName + "=" + interfaceName);
+					continue;
+				}
+
+				// If we have descriptors, we have analyzed the component.
+				// So why not check the methods
+				if (descriptors.size() > 0) {
+					// Verify that the bind method exists
+					if (!descriptors.contains(bind))
+						error("The bind method %s for %s not defined", bind, referenceName);
+
+					// Check if the unbind method exists
+					if (!descriptors.contains(unbind)) {
+						if (unbindCalculated)
+							// remove it
+							unbind = null;
+						else
+							error("The unbind method %s for %s not defined", unbind, referenceName);
+					}
+				}
+				// Check tje cardinality by looking at the last
+				// character of the value
+				char c = interfaceName.charAt(interfaceName.length() - 1);
+				if ("?+*~".indexOf(c) >= 0) {
+					if (c == '?' || c == '*' || c == '~')
+						optional.add(referenceName);
+					if (c == '+' || c == '*')
+						multiple.add(referenceName);
+					if (c == '+' || c == '*' || c == '?')
+						dynamic.add(referenceName);
+					interfaceName = interfaceName.substring(0, interfaceName.length() - 1);
+				}
+
+				// Parse the target from the interface name
+				// The target is a filter.
+				String target = null;
+				Matcher m = REFERENCE.matcher(interfaceName);
+				if (m.matches()) {
+					interfaceName = m.group(1);
+					target = m.group(2);
+				}
+
+				analyzer.referTo(interfaceName);
+
+				pw.printf("  <reference name='%s'", referenceName);
+				pw.printf(" interface='%s'", interfaceName);
+
+				String cardinality = optional.contains(referenceName) ? "0" : "1";
+				cardinality += "..";
+				cardinality += multiple.contains(referenceName) ? "n" : "1";
+				if (!cardinality.equals("1..1"))
+					pw.print(" cardinality='" + cardinality + "'");
+
+				if (bind != null) {
+					pw.printf(" bind='%s'", bind);
+					if (unbind != null) {
+						pw.printf(" unbind='%s'", unbind);
+					}
+				}
+
+				if (dynamic.contains(referenceName)) {
+					pw.print(" policy='dynamic'");
+				}
+
+				if (target != null) {
+					// Filter filter = new Filter(target);
+					// if (filter.verify() == null)
+					// pw.print(" target='" + filter.toString() + "'");
+					// else
+					// error("Target for " + referenceName
+					// + " is not a correct filter: " + target + " "
+					// + filter.verify());
+					pw.print(" target='" + escape(target) + "'");
+				}
+				pw.println("/>");
+			}
+		}
+	}
+
+	/**
+	 * Escape a string, do entity conversion.
+	 */
+	static String escape(String s) {
+		StringBuffer sb = new StringBuffer();
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			switch (c) {
+			case '<':
+				sb.append("&lt;");
+				break;
+			case '>':
+				sb.append("&gt;");
+				break;
+			case '&':
+				sb.append("&amp;");
+				break;
+			case '\'':
+				sb.append("&quot;");
+				break;
+			default:
+				sb.append(c);
+				break;
+			}
+		}
+		return sb.toString();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java b/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
new file mode 100644
index 0000000..bc33dc2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
@@ -0,0 +1,95 @@
+package aQute.bnd.make.coverage;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+
+/**
+ * This class can create a coverage table between two classspaces. The
+ * destination class space is used to create a table of methods. All source
+ * methods that refer to a specific dest are then filled into the table.
+ * 
+ */
+public class Coverage {
+
+    /**
+     * Create a cross reference table from source to dest.
+     * 
+     * @param source
+     *            The methods that refer to dest
+     * @param dest
+     *            The methods that are being referred to
+     * @return A mapping of source methods to destination methods.
+     * @throws IOException
+     */
+    public static Map<MethodDef, List<MethodDef>> getCrossRef(
+            Collection<Clazz> source, Collection<Clazz> dest)
+            throws Exception {
+        final Map<MethodDef, List<MethodDef>> catalog = buildCatalog(dest);
+        crossRef(source, catalog);
+        return catalog;
+    }
+
+    private static void crossRef(Collection<Clazz> source,
+            final Map<MethodDef, List<MethodDef>> catalog) throws Exception {
+        for (final Clazz clazz : source) {
+            clazz.parseClassFileWithCollector(new ClassDataCollector() {
+                MethodDef source;
+
+                public void implementsInterfaces(String names[]) {
+                    MethodDef def = new MethodDef(0, clazz.getFQN(),
+                            "<implements>", "()V");
+                    for (String interfaceName : names) {
+                        interfaceName = Clazz.internalToFqn(interfaceName);
+                        for (Map.Entry<MethodDef, List<MethodDef>> entry : catalog
+                                .entrySet()) {
+                            String catalogClass = entry.getKey().clazz;
+                            List<MethodDef> references = entry.getValue();
+
+                            if (catalogClass.equals(interfaceName)) {
+                                references.add(def);
+                            }
+                        }
+                    }
+                }
+
+                // Method definitions
+                public void method(MethodDef source) {
+                    this.source = source;
+                }
+
+                public void reference(MethodDef reference) {
+                    List<MethodDef> references = catalog.get(reference);
+                    if (references != null) {
+                        references.add(source);
+                    }
+                }
+            });
+        }
+    }
+
+    private static Map<MethodDef, List<MethodDef>> buildCatalog(
+            Collection<Clazz> sources) throws Exception {
+        final Map<MethodDef, List<MethodDef>> catalog = new TreeMap<MethodDef, List<MethodDef>>();
+        for (final Clazz clazz : sources) {
+            clazz.parseClassFileWithCollector(new ClassDataCollector() {
+
+                public boolean classStart(int access, String name) {
+                    return clazz.isPublic();
+                }
+
+                public void method(MethodDef source) {
+                    if (java.lang.reflect.Modifier.isPublic(source.access)
+                            || Modifier.isProtected(source.access))
+                        catalog.put(source, new ArrayList<MethodDef>());
+                }
+
+            });
+        }
+        return catalog;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/coverage/CoverageResource.java b/bundleplugin/src/main/java/aQute/bnd/make/coverage/CoverageResource.java
new file mode 100644
index 0000000..3827391
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/coverage/CoverageResource.java
@@ -0,0 +1,92 @@
+package aQute.bnd.make.coverage;
+
+import static aQute.bnd.make.coverage.Coverage.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+import aQute.lib.tag.*;
+
+/**
+ * Creates an XML Coverage report. This class can be used as a resource so the
+ * report is created only when the JAR is written.
+ * 
+ */
+public class CoverageResource extends WriteResource {
+    Collection<Clazz> testsuite;
+    Collection<Clazz> service;
+
+    public CoverageResource(Collection<Clazz> testsuite,
+            Collection<Clazz> service) {
+        this.testsuite = testsuite;
+        this.service = service;
+    }
+
+    @Override
+    public long lastModified() {
+        return 0;
+    }
+
+    @Override
+    public void write(OutputStream out) throws IOException {
+        try {
+            Map<MethodDef, List<MethodDef>> table = getCrossRef(testsuite,
+                    service);
+            Tag coverage = toTag(table);
+            PrintWriter pw = new PrintWriter(new OutputStreamWriter(out,
+                    Constants.DEFAULT_CHARSET));
+            try {
+                coverage.print(0, pw);
+            } finally {
+                pw.flush();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static Tag toTag(Map<MethodDef, List<MethodDef>> catalog) {
+        Tag coverage = new Tag("coverage");
+        String currentClass = null;
+        Tag classTag = null;
+
+        for (Map.Entry<MethodDef, List<MethodDef>> m : catalog.entrySet()) {
+            String className = m.getKey().clazz;
+            if (!className.equals(currentClass)) {
+                classTag = new Tag("class");
+                classTag.addAttribute("name", className);
+                classTag.addAttribute("package", Clazz.getPackage(className));
+                classTag.addAttribute("short", Clazz.getShortName(className));
+                coverage.addContent(classTag);
+                currentClass = className;
+            }
+            Tag method = doMethod(new Tag("method"), m.getKey());
+            classTag.addContent(method);
+            for (MethodDef r : m.getValue()) {
+                Tag ref = doMethod(new Tag("ref"), r);
+                method.addContent(ref);
+            }
+        }
+        return coverage;
+    }
+
+    private static Tag doMethod(Tag tag, MethodDef method) {
+        tag.addAttribute("pretty", method.getPretty());
+        if (Modifier.isPublic(method.access))
+            tag.addAttribute("public", true);
+        if (Modifier.isStatic(method.access))
+            tag.addAttribute("static", true);
+        if (Modifier.isProtected(method.access))
+            tag.addAttribute("protected", true);
+        if (Modifier.isInterface(method.access))
+            tag.addAttribute("interface", true);
+        tag.addAttribute("constructor", method.isConstructor());
+        if (!method.isConstructor())
+            tag.addAttribute("name", method.name);
+        tag.addAttribute("descriptor", method.descriptor);
+        return tag;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetaTypeReader.java b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetaTypeReader.java
new file mode 100644
index 0000000..94a65ae
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetaTypeReader.java
@@ -0,0 +1,341 @@
+package aQute.bnd.make.metatype;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.metatype.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.tag.*;
+import aQute.libg.generics.*;
+
+public class MetaTypeReader extends ClassDataCollector implements Resource {
+	final Analyzer				reporter;
+	Clazz						clazz;
+	String						interfaces[];
+	Tag							metadata	= new Tag("metatype:MetaData", new String[] {
+			"xmlns:metatype", "http://www.osgi.org/xmlns/metatype/v1.1.0" });
+	Tag							ocd			= new Tag(metadata, "OCD");
+	Tag							designate	= new Tag(metadata, "Designate");
+	Tag							object		= new Tag(designate, "Object");
+
+	// Resource
+	String						extra;
+
+	// One time init
+	boolean						finished;
+
+	// Designate
+	boolean						override;
+	String						designatePid;
+	boolean						factory;
+
+	// AD
+	Map<MethodDef, Meta.AD>	methods		= new LinkedHashMap<MethodDef, Meta.AD>();
+
+	// OCD
+	Annotation					ocdAnnotation;
+
+	MethodDef					method;
+
+	public MetaTypeReader(Clazz clazz, Analyzer reporter) {
+		this.clazz = clazz;
+		this.reporter = reporter;
+	}
+
+	public void annotation(Annotation annotation) {
+		try {
+			Meta.OCD ocd = annotation.getAnnotation(Meta.OCD.class);
+			Meta.AD ad = annotation.getAnnotation(Meta.AD.class);
+			if (ocd != null) {
+				this.ocdAnnotation = annotation;
+			}
+			if (ad != null) {
+				assert method != null;
+				methods.put(method, ad);
+			}
+		} catch (Exception e) {
+			reporter.error("Error during annotation parsing %s : %s", clazz, e);
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @param id
+	 * @param name
+	 * @param cardinality
+	 * @param required
+	 * @param deflt
+	 * @param type
+	 * @param max
+	 * @param min
+	 * @param optionLabels
+	 * @param optionValues
+	 */
+
+	static Pattern	COLLECTION	= Pattern
+										.compile("(.*(Collection|Set|List|Queue|Stack|Deque))<(L.+;)>");
+
+	private void addMethod(MethodDef method, Meta.AD ad) throws Exception {
+
+		// Set all the defaults.
+		String rtype = method.getReturnType();
+		String id = Configurable.mangleMethodName(method.name);
+		String name = Clazz.unCamel(id);
+
+		int cardinality = 0;
+
+		if (rtype.endsWith("[]")) {
+			cardinality = Integer.MAX_VALUE;
+			rtype = rtype.substring(0, rtype.length() - 2);
+		}
+		if (rtype.indexOf('<') > 0) {
+			if (cardinality != 0)
+				reporter.error(
+						"AD for %s.%s uses an array of collections in return type (%s), Metatype allows either Vector or array",
+						clazz.getFQN(), method.name, method.getReturnType());
+			Matcher m = COLLECTION.matcher(rtype);
+			if (m.matches()) {
+				rtype = Clazz.objectDescriptorToFQN(m.group(3));
+				cardinality = Integer.MIN_VALUE;
+			}
+		}
+
+		Meta.Type type = getType(rtype);
+		
+		boolean required = ad ==null || ad.required();
+		String deflt = null;
+		String max = null;
+		String min = null;
+		String[] optionLabels = null;
+		String[] optionValues = null;
+		String description = null;
+
+		Clazz c = reporter.findClass(Clazz.fqnToPath(rtype));
+		if (c != null && c.isEnum()) {
+			optionValues = parseOptionValues(c);
+		}
+
+		// Now parse the annotation for any overrides
+
+		if (ad != null) {
+			if (ad.id() != null)
+				id = ad.id();
+			if (ad.name() != null)
+				name = ad.name();
+			if (ad.cardinality() != 0)
+				cardinality = ad.cardinality();
+			if (ad.type() != null)
+				type = ad.type();
+			if (ad.required() || ad.deflt() == null)
+				required = true;
+
+			if (ad.description() != null)
+				description = ad.description();
+
+			if (ad.optionLabels() != null)
+				optionLabels = ad.optionLabels();
+			if (ad.optionValues() != null )
+				optionValues = ad.optionValues();
+
+			if (ad.min() != null)
+				min = ad.min();
+			if (ad.max() != null)
+				max = ad.max();
+
+			if (ad.deflt() != null)
+				deflt = ad.deflt();
+		}
+
+		if (optionValues != null) {
+			if (optionLabels == null || optionLabels.length == 0) {
+				optionLabels = new String[optionValues.length];
+				for (int i = 0; i < optionValues.length; i++)
+					optionLabels[i] = Clazz.unCamel(optionValues[i]);
+			}
+
+			if (optionLabels.length != optionValues.length) {
+				reporter.error("Option labels and option values not the same length for %s", id);
+				optionLabels = optionValues;
+			}
+		}
+
+		Tag adt = new Tag(this.ocd, "AD");
+		adt.addAttribute("name", name);
+		adt.addAttribute("id", id);
+		adt.addAttribute("cardinality", cardinality);
+		adt.addAttribute("required", required);
+		adt.addAttribute("default", deflt);
+		adt.addAttribute("type", type);
+		adt.addAttribute("max", max);
+		adt.addAttribute("min", min);
+		adt.addAttribute("description", description);
+
+		if (optionLabels != null) {
+			for (int i = 0; i < optionLabels.length; i++) {
+				Tag option = new Tag(adt, "Option");
+				option.addAttribute("label", optionLabels[i]);
+				option.addAttribute("value", optionValues[i]);
+			}
+		}
+	}
+
+	private String[] parseOptionValues(Clazz c) throws Exception {
+		final List<String> values = Create.list();
+
+		c.parseClassFileWithCollector(new ClassDataCollector() {
+			public void field(Clazz.FieldDef def) {
+				if (def.isEnum()) {
+					values.add(def.name);
+				}
+			}
+		});
+		return values.toArray(new String[values.size()]);
+	}
+
+	Meta.Type getType(String rtype) {
+		if (rtype.endsWith("[]")) {
+			rtype = rtype.substring(0, rtype.length() - 2);
+			if (rtype.endsWith("[]"))
+				throw new IllegalArgumentException("Can only handle array of depth one");
+		}
+
+		if ("boolean".equals(rtype) || Boolean.class.getName().equals(rtype))
+			return Meta.Type.Boolean;
+		else if ("byte".equals(rtype) || Byte.class.getName().equals(rtype))
+			return Meta.Type.Byte;
+		else if ("char".equals(rtype) || Character.class.getName().equals(rtype))
+			return Meta.Type.Character;
+		else if ("short".equals(rtype) || Short.class.getName().equals(rtype))
+			return Meta.Type.Short;
+		else if ("int".equals(rtype) || Integer.class.getName().equals(rtype))
+			return Meta.Type.Integer;
+		else if ("long".equals(rtype) || Long.class.getName().equals(rtype))
+			return Meta.Type.Long;
+		else if ("float".equals(rtype) || Float.class.getName().equals(rtype))
+			return Meta.Type.Float;
+		else if ("double".equals(rtype) || Double.class.getName().equals(rtype))
+			return Meta.Type.Double;
+		else
+			return Meta.Type.String;
+	}
+
+	@Override public void method(MethodDef mdef) {
+		method = mdef;
+		methods.put(mdef, null);
+	}
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public long lastModified() {
+		return 0;
+	}
+
+	public InputStream openInputStream() throws IOException {
+		final PipedInputStream pin = new PipedInputStream();
+		final PipedOutputStream pout = new PipedOutputStream(pin);
+		getExecutor().execute(new Runnable() {
+			public void run() {
+				try {
+					write(pout);
+				} catch (IOException e) {
+					// Cause an exception in the other end
+					IO.close(pin);
+				}
+				IO.close(pout);
+			}
+		});
+		return pin;
+	}
+
+	private Executor getExecutor() {
+		return reporter.getPlugin(Executor.class);
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+
+	public void write(OutputStream out) throws IOException {
+		try {
+			finish();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+		PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
+		pw.println("<?xml version='1.0'?>");
+		metadata.print(0, pw);
+		pw.flush();
+	}
+
+	void finish() throws Exception {
+		if (!finished) {
+			finished = true;
+			clazz.parseClassFileWithCollector(this);
+			Meta.OCD ocd = null;
+			if (this.ocdAnnotation != null)
+				ocd = this.ocdAnnotation.getAnnotation(Meta.OCD.class);
+			else
+				ocd = Configurable.createConfigurable(Meta.OCD.class,
+						new HashMap<String, Object>());
+
+			// defaults
+			String id = clazz.getFQN();
+			String name = Clazz.unCamel(Clazz.getShortName(clazz.getFQN()));
+			String description = null;
+			String localization = id;
+			boolean factory = this.factory;
+
+			if (ocd.id() != null)
+				id = ocd.id();
+
+			
+			if (ocd.name() != null)
+				name = ocd.name();
+
+			if (ocd.localization() != null)
+				localization = ocd.localization();
+
+			if (ocd.description() != null)
+				description = ocd.description();
+
+			String pid = id;
+			if (override) {
+				pid = this.designatePid;
+				factory = this.factory;
+				id = this.designatePid; // for the felix problems
+			} else {
+				if (ocdAnnotation.get("factory") != null) {
+					factory = true;
+				}
+			}
+
+			this.ocd.addAttribute("name", name);
+			this.ocd.addAttribute("id", id);
+			this.ocd.addAttribute("description", description);
+			this.ocd.addAttribute("localization", localization);
+
+			// do ADs
+			for (Map.Entry<MethodDef, Meta.AD> entry : methods.entrySet())
+				addMethod(entry.getKey(), entry.getValue());
+
+			this.designate.addAttribute("pid", pid);
+			if (factory)
+				this.designate.addAttribute("factoryPid", pid);
+
+			this.object.addAttribute("ocdref", id);
+
+		}
+	}
+
+	public void setDesignate(String pid, boolean factory) {
+		this.override = true;
+		this.factory = factory;
+		this.designatePid = pid;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetatypePlugin.java b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetatypePlugin.java
new file mode 100644
index 0000000..4801ec9
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetatypePlugin.java
@@ -0,0 +1,35 @@
+package aQute.bnd.make.metatype;
+
+import java.util.*;
+
+import aQute.bnd.annotation.metatype.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+
+/**
+ * This class is responsible for meta type types. It is a plugin that can 
+ * @author aqute
+ *
+ */
+public class MetatypePlugin implements AnalyzerPlugin {
+
+	public boolean analyzeJar(Analyzer analyzer) throws Exception {
+
+		Map<String, Map<String, String>> map = analyzer.parseHeader(analyzer
+				.getProperty(Constants.METATYPE));
+
+		Jar jar = analyzer.getJar();
+		for (String name : map.keySet()) {
+			Collection<Clazz> metatypes = analyzer.getClasses("", QUERY.ANNOTATION.toString(),
+					Meta.OCD.class.getName(), // 
+					QUERY.NAMED.toString(), name //
+					);
+			for (Clazz c : metatypes) {
+				jar.putResource("OSGI-INF/metatype/" + c.getFQN() + ".xml", new MetaTypeReader(c,
+						analyzer));
+			}
+		}
+		return false;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/BsnToMavenPath.java b/bundleplugin/src/main/java/aQute/bnd/maven/BsnToMavenPath.java
new file mode 100644
index 0000000..6dc716c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/BsnToMavenPath.java
@@ -0,0 +1,5 @@
+package aQute.bnd.maven;
+
+public interface BsnToMavenPath {
+    String[] getGroupAndArtifact(String bsn);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
new file mode 100644
index 0000000..c274ca5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
@@ -0,0 +1,615 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.maven.support.*;
+import aQute.bnd.maven.support.Pom.*;
+import aQute.bnd.settings.*;
+import aQute.lib.collections.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.header.*;
+
+public class MavenCommand extends Processor {
+	final Settings	settings	= new Settings();
+	File			temp;
+
+	public MavenCommand() {
+	}
+
+	public MavenCommand(Processor p) {
+		super(p);
+	}
+
+	/**
+	 * maven deploy [-url repo] [-passphrase passphrase] [-homedir homedir]
+	 * [-keyname keyname] bundle ...
+	 * 
+	 * @param args
+	 * @param i
+	 * @throws Exception
+	 */
+	public void run(String args[], int i) throws Exception {
+		temp = new File("maven-bundle");
+
+		if (i >= args.length) {
+			help();
+			return;
+		}
+
+		while (i < args.length && args[i].startsWith("-")) {
+			String option = args[i];
+			trace("option " + option);
+			if (option.equals("-temp"))
+				temp = getFile(args[++i]);
+			else {
+				help();
+				error("Invalid option " + option);
+			}
+			i++;
+		}
+
+		String cmd = args[i++];
+
+		trace("temp dir " + temp);
+		IO.delete(temp);
+		temp.mkdirs();
+		if (!temp.isDirectory())
+			throw new IOException("Cannot create temp directory");
+
+		if (cmd.equals("settings"))
+			settings();
+		else if (cmd.equals("help"))
+			help();
+		else if (cmd.equals("bundle"))
+			bundle(args, i);
+		else if (cmd.equals("view"))
+			view(args, i);
+		else
+			error("No such command %s, type help", cmd);
+	}
+
+	private void help() {
+		System.err.println("Usage:\n");
+		System.err
+				.println("  maven \n" //
+						+ "  [-temp <dir>]            use as temp directory\n" //
+						+ "  settings                 show maven settings\n" //
+						+ "  bundle                   turn a bundle into a maven bundle\n" //
+						+ "    [-properties <file>]   provide properties, properties starting with javadoc are options for javadoc, like javadoc-tag=...\n"
+						+ "    [-javadoc <file|url>]  where to find the javadoc (zip/dir), otherwise generated\n" //
+						+ "    [-source <file|url>]   where to find the source (zip/dir), otherwise from OSGI-OPT/src\n" //
+						+ "    [-scm <url>]           required scm in pom, otherwise from Bundle-SCM\n" //
+						+ "    [-url <url>]           required project url in pom\n" //
+						+ "    [-bsn bsn]             overrides bsn\n" //
+						+ "    [-version <version>]   overrides version\n" //
+						+ "    [-developer <email>]   developer email\n" //
+						+ "    [-nodelete]            do not delete temp files\n" //
+						+ "    [-passphrase <gpgp passphrase>] signer password\n"//
+						+ "        <file|url> ");
+	}
+
+	/**
+	 * Show the maven settings
+	 * 
+	 * @throws FileNotFoundException
+	 * @throws Exception
+	 */
+	private void settings() throws FileNotFoundException, Exception {
+		File userHome = new File(System.getProperty("user.home"));
+		File m2 = new File(userHome, ".m2");
+		if (!m2.isDirectory()) {
+			error("There is no m2 directory at %s", userHome);
+			return;
+		}
+		File settings = new File(m2, "settings.xml");
+		if (!settings.isFile()) {
+			error("There is no settings file at '%s'", settings.getAbsolutePath());
+			return;
+		}
+
+		FileReader rdr = new FileReader(settings);
+
+		LineCollection lc = new LineCollection(new BufferedReader(rdr));
+		while (lc.hasNext()) {
+			System.out.println(lc.next());
+		}
+	}
+
+	/**
+	 * Create a maven bundle.
+	 * 
+	 * @param args
+	 * @param i
+	 * @throws Exception
+	 */
+	private void bundle(String args[], int i) throws Exception {
+		List<String> developers = new ArrayList<String>();
+		Properties properties = new Properties();
+
+		String scm = null;
+		String passphrase = null;
+		String javadoc = null;
+		String source = null;
+		String output = "bundle.jar";
+		String url = null;
+		String artifact = null;
+		String group = null;
+		String version = null;
+		boolean nodelete = false;
+
+		while (i < args.length && args[i].startsWith("-")) {
+			String option = args[i++];
+			trace("bundle option %s", option);
+			if (option.equals("-scm"))
+				scm = args[i++];
+			else if (option.equals("-group"))
+				group = args[i++];
+			else if (option.equals("-artifact"))
+				artifact = args[i++];
+			else if (option.equals("-version"))
+				version = args[i++];
+			else if (option.equals("-developer"))
+				developers.add(args[i++]);
+			else if (option.equals("-passphrase")) {
+				passphrase = args[i++];
+			} else if (option.equals("-url")) {
+				url = args[i++];
+			} else if (option.equals("-javadoc"))
+				javadoc = args[i++];
+			else if (option.equals("-source"))
+				source = args[i++];
+			else if (option.equals("-output"))
+				output = args[i++];
+			else if (option.equals("-nodelete"))
+				nodelete=true;
+			else if (option.startsWith("-properties")) {
+				InputStream in = new FileInputStream(args[i++]);
+				try {
+					properties.load(in);
+				} catch (Exception e) {
+					in.close();
+				}
+			}
+		}
+
+		if (developers.isEmpty()) {
+			String email = settings.globalGet(Settings.EMAIL, null);
+			if (email == null)
+				error("No developer email set, you can set global default email with: bnd global email Peter.Kriens@aQute.biz");
+			else
+				developers.add(email);
+		}
+
+		if (i == args.length) {
+			error("too few arguments, no bundle specified");
+			return;
+		}
+
+		if (i != args.length - 1) {
+			error("too many arguments, only one bundle allowed");
+			return;
+		}
+
+		String input = args[i++];
+
+		Jar binaryJar = getJarFromFileOrURL(input);
+		trace("got %s", binaryJar);
+		if (binaryJar == null) {
+			error("JAR does not exist: %s", input);
+			return;
+		}
+
+		File original = getFile(temp, "original");
+		original.mkdirs();
+		binaryJar.expand(original);
+		binaryJar.calcChecksums(null);
+
+		Manifest manifest = binaryJar.getManifest();
+		trace("got manifest");
+
+		PomFromManifest pom = new PomFromManifest(manifest);
+
+		if (scm != null)
+			pom.setSCM(scm);
+		if (url != null)
+			pom.setURL(url);
+		if (artifact != null)
+			pom.setArtifact(artifact);
+		if (artifact != null)
+			pom.setGroup(group);
+		if (version != null)
+			pom.setVersion(version);
+		trace(url);
+		for (String d : developers)
+			pom.addDeveloper(d);
+
+		Set<String> exports = OSGiHeader.parseHeader(
+				manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE)).keySet();
+
+		Jar sourceJar;
+		if (source == null) {
+			trace("Splitting source code");
+			sourceJar = new Jar("source");
+			for (Map.Entry<String, Resource> entry : binaryJar.getResources().entrySet()) {
+				if (entry.getKey().startsWith("OSGI-OPT/src")) {
+					sourceJar.putResource(entry.getKey().substring("OSGI-OPT/src/".length()),
+							entry.getValue());
+				}
+			}
+			copyInfo(binaryJar, sourceJar, "source");
+		} else {
+			sourceJar = getJarFromFileOrURL(source);
+		}
+		sourceJar.calcChecksums(null);
+		
+		Jar javadocJar;
+		if (javadoc == null) {
+			trace("creating javadoc because -javadoc not used");
+			javadocJar = javadoc(getFile(original, "OSGI-OPT/src"), exports, manifest, properties);
+			if (javadocJar == null) {
+				error("Cannot find source code in OSGI-OPT/src to generate Javadoc");
+				return;
+			}
+			copyInfo(binaryJar, javadocJar, "javadoc");
+		} else {
+			trace("Loading javadoc externally %s", javadoc);
+			javadocJar = getJarFromFileOrURL(javadoc);
+		}
+		javadocJar.calcChecksums(null);
+		
+		addClose(binaryJar);
+		addClose(sourceJar);
+		addClose(javadocJar);
+
+		trace("creating bundle dir");
+		File bundle = new File(temp, "bundle");
+		bundle.mkdirs();
+
+		String prefix = pom.getArtifactId() + "-" + pom.getVersion();
+		File binaryFile = new File(bundle, prefix + ".jar");
+		File sourceFile = new File(bundle, prefix + "-sources.jar");
+		File javadocFile = new File(bundle, prefix + "-javadoc.jar");
+		File pomFile = new File(bundle, "pom.xml").getAbsoluteFile();
+		trace("creating output files %s, %s,%s, and %s", binaryFile, sourceFile, javadocFile,
+				pomFile);
+
+		IO.copy(pom.openInputStream(), pomFile);
+		trace("copied pom");
+
+		trace("writing binary %s", binaryFile);
+		binaryJar.write(binaryFile);
+
+		trace("writing source %s", sourceFile);
+		sourceJar.write(sourceFile);
+
+		trace("writing javadoc %s", javadocFile);
+		javadocJar.write(javadocFile);
+
+		sign(binaryFile, passphrase);
+		sign(sourceFile, passphrase);
+		sign(javadocFile, passphrase);
+		sign(pomFile, passphrase);
+
+		trace("create bundle");
+		Jar bundleJar = new Jar(bundle);
+		addClose(bundleJar);
+		File outputFile = getFile(output);
+		bundleJar.write(outputFile);
+		trace("created bundle %s", outputFile);
+
+		binaryJar.close();
+		sourceJar.close();
+		javadocJar.close();
+		bundleJar.close();
+		if (!nodelete)
+			IO.delete(temp);
+	}
+
+	private void copyInfo(Jar source, Jar dest, String type) throws Exception {
+		source.ensureManifest();
+		dest.ensureManifest();
+		copyInfoResource( source, dest, "LICENSE");
+		copyInfoResource( source, dest, "LICENSE.html");
+		copyInfoResource( source, dest, "about.html");
+		
+		Manifest sm = source.getManifest();
+		Manifest dm = dest.getManifest();
+		copyInfoHeader( sm, dm, "Bundle-Description","");
+		copyInfoHeader( sm, dm, "Bundle-Vendor","");
+		copyInfoHeader( sm, dm, "Bundle-Copyright","");
+		copyInfoHeader( sm, dm, "Bundle-DocURL","");
+		copyInfoHeader( sm, dm, "Bundle-License","");
+		copyInfoHeader( sm, dm, "Bundle-Name", " " + type);
+		copyInfoHeader( sm, dm, "Bundle-SymbolicName", "." + type);
+		copyInfoHeader( sm, dm, "Bundle-Version", "");
+	}
+
+	private void copyInfoHeader(Manifest sm, Manifest dm, String key, String value) {
+		String v = sm.getMainAttributes().getValue(key);
+		if ( v == null) {
+			trace("no source for " + key);
+			return;
+		}
+		
+		if ( dm.getMainAttributes().getValue(key) != null) {
+			trace("already have " + key );
+			return;
+		}
+		
+		dm.getMainAttributes().putValue(key, v + value);
+	}
+
+	private void copyInfoResource(Jar source, Jar dest, String type) {
+		if ( source.getResources().containsKey(type) && !dest.getResources().containsKey(type))
+			dest.putResource(type, source.getResource(type));
+	}
+
+	/**
+	 * @return
+	 * @throws IOException
+	 * @throws MalformedURLException
+	 */
+	protected Jar getJarFromFileOrURL(String spec) throws IOException, MalformedURLException {
+		Jar jar;
+		File jarFile = getFile(spec);
+		if (jarFile.exists()) {
+			jar = new Jar(jarFile);
+		} else {
+			URL url = new URL(spec);
+			InputStream in = url.openStream();
+			try {
+				jar = new Jar(url.getFile(), in);
+			} finally {
+				in.close();
+			}
+		}
+		addClose(jar);
+		return jar;
+	}
+
+	private void sign(File file, String passphrase) throws Exception {
+		trace("signing %s", file);
+		File asc = new File(file.getParentFile(), file.getName() + ".asc");
+		asc.delete();
+
+		Command command = new Command();
+		command.setTrace();
+
+		command.add(getProperty("gpgp", "gpg"));
+		if (passphrase != null)
+			command.add("--passphrase", passphrase);
+		command.add("-ab", "--sign"); // not the -b!!
+		command.add(file.getAbsolutePath());
+		System.out.println(command);
+		StringBuffer stdout = new StringBuffer();
+		StringBuffer stderr = new StringBuffer();
+		int result = command.execute(stdout, stderr);
+		if (result != 0) {
+			error("gpg signing %s failed because %s", file, "" + stdout + stderr);
+		}
+	}
+
+	private Jar javadoc(File source, Set<String> exports, Manifest m, Properties p)
+			throws Exception {
+		File tmp = new File(temp, "javadoc");
+		tmp.mkdirs();
+
+		Command command = new Command();
+		command.add(getProperty("javadoc", "javadoc"));
+		command.add("-quiet");
+		command.add("-protected");
+		// command.add("-classpath");
+		// command.add(binary.getAbsolutePath());
+		command.add("-d");
+		command.add(tmp.getAbsolutePath());
+		command.add("-charset");
+		command.add("UTF-8");
+		command.add("-sourcepath");
+		command.add(source.getAbsolutePath());
+
+		Attributes attr = m.getMainAttributes();
+		Properties pp = new Properties(p);
+		set(pp, "-doctitle", description(attr));
+		set(pp, "-header", description(attr));
+		set(pp, "-windowtitle", name(attr));
+		set(pp, "-bottom", copyright(attr));
+		set(pp, "-footer", license(attr));
+
+		command.add("-tag");
+		command.add("Immutable:t:Immutable");
+		command.add("-tag");
+		command.add("ThreadSafe:t:ThreadSafe");
+		command.add("-tag");
+		command.add("NotThreadSafe:t:NotThreadSafe");
+		command.add("-tag");
+		command.add("GuardedBy:mf:Guarded By:");
+		command.add("-tag");
+		command.add("security:m:Required Permissions");
+		command.add("-tag");
+		command.add("noimplement:t:Consumers of this API must not implement this interface");
+
+		for (Enumeration<?> e = pp.propertyNames(); e.hasMoreElements();) {
+			String key = (String) e.nextElement();
+			String value = pp.getProperty(key);
+
+			if (key.startsWith("javadoc")) {
+				key = key.substring("javadoc".length());
+				removeDuplicateMarker(key);
+				command.add(key);
+				command.add(value);
+			}
+		}
+		for (String packageName : exports) {
+			command.add(packageName);
+		}
+
+		StringBuffer out = new StringBuffer();
+		StringBuffer err = new StringBuffer();
+
+		System.out.println(command);
+
+		int result = command.execute(out, err);
+		if (result != 0) {
+			warning("Error during execution of javadoc command: %s\n******************\n%s", out,
+					err);
+		}
+		Jar jar = new Jar(tmp);
+		addClose(jar);
+		return jar;
+	}
+
+	/**
+	 * Generate a license string
+	 * 
+	 * @param attr
+	 * @return
+	 */
+	private String license(Attributes attr) {
+		Map<String, Map<String, String>> map = Processor.parseHeader(
+				attr.getValue(Constants.BUNDLE_LICENSE), null);
+		if (map.isEmpty())
+			return null;
+
+		StringBuilder sb = new StringBuilder();
+		String sep = "Licensed under ";
+		for (Map.Entry<String, Map<String, String>> entry : map.entrySet()) {
+			sb.append(sep);
+			String key = entry.getKey();
+			String link = entry.getValue().get("link");
+			String description = entry.getValue().get("description");
+
+			if (description == null)
+				description = key;
+
+			if (link != null) {
+				sb.append("<a href='");
+				sb.append(link);
+				sb.append("'>");
+			}
+			sb.append(description);
+			if (link != null) {
+				sb.append("</a>");
+			}
+			sep = ",<br/>";
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * Generate the copyright statement.
+	 * 
+	 * @param attr
+	 * @return
+	 */
+	private String copyright(Attributes attr) {
+		return attr.getValue(Constants.BUNDLE_COPYRIGHT);
+	}
+
+	private String name(Attributes attr) {
+		String name = attr.getValue(Constants.BUNDLE_NAME);
+		if (name == null)
+			name = attr.getValue(Constants.BUNDLE_SYMBOLICNAME);
+		return name;
+	}
+
+	private String description(Attributes attr) {
+		String descr = attr.getValue(Constants.BUNDLE_DESCRIPTION);
+		if (descr == null)
+			descr = attr.getValue(Constants.BUNDLE_NAME);
+		if (descr == null)
+			descr = attr.getValue(Constants.BUNDLE_SYMBOLICNAME);
+		return descr;
+	}
+
+	private void set(Properties pp, String option, String defaultValue) {
+		String key = "javadoc" + option;
+		String existingValue = pp.getProperty(key);
+		if (existingValue != null)
+			return;
+
+		pp.setProperty(key, defaultValue);
+	}
+
+	
+	/**
+	 * View - Show the dependency details of an artifact
+	 */
+	
+
+	static Executor executor = Executors.newCachedThreadPool();
+	static Pattern GROUP_ARTIFACT_VERSION = Pattern.compile("([^+]+)\\+([^+]+)\\+([^+]+)");
+	void view( String args[], int i) throws Exception {
+		Maven maven = new Maven(executor);
+		OutputStream out = System.out;
+		
+		List<URI>	urls = new ArrayList<URI>();
+		
+		while ( i < args.length && args[i].startsWith("-")) {
+			if( "-r".equals(args[i])) {
+				URI uri = new URI(args[++i]);
+				urls.add( uri );
+				System.out.println("URI for repo " + uri);
+			}
+			else
+				if ( "-o".equals(args[i])) {
+					out = new FileOutputStream(args[++i]);
+				}
+				else
+					throw new IllegalArgumentException("Unknown option: " + args[i]);
+			
+			i++;
+		}
+		
+		URI[] urls2 = urls.toArray(new URI[urls.size()]);
+		PrintWriter pw = new PrintWriter(out);
+		
+		while ( i < args.length) {
+			String ref = args[i++];
+			pw.println("Ref " + ref);
+			
+			Matcher matcher = GROUP_ARTIFACT_VERSION.matcher(ref);
+			if (matcher.matches()) {
+				
+				String group = matcher.group(1);
+				String artifact = matcher.group(2);
+				String version = matcher.group(3);
+				CachedPom pom = maven.getPom(group, artifact, version, urls2);
+				
+				Builder a = new Builder();
+				a.setProperty("Private-Package", "*");
+				Set<Pom> dependencies = pom.getDependencies(Scope.compile, urls2);
+				for ( Pom dep : dependencies ) {
+					System.out.printf( "%20s %-20s %10s\n", dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
+					a.addClasspath(dep.getArtifact());
+				}
+				pw.println(a.getClasspath());
+				a.build();
+
+				TreeSet<String> sorted = new TreeSet<String>( a.getImports().keySet());
+				for ( String p :sorted) {
+					pw.printf("%-40s\n",p);
+				}
+//				for ( Map.Entry<String, Set<String>> entry : a.getUses().entrySet()) {
+//					String from = entry.getKey();
+//					for ( String uses : entry.getValue()) {
+//						System.out.printf("%40s %s\n", from, uses);
+//						from = "";
+//					}
+//				}
+				a.close();
+			} else
+				System.err.println("Wrong, must look like group+artifact+version, is " + ref);
+			
+		}
+	}
+	
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenDependencyGraph.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDependencyGraph.java
new file mode 100644
index 0000000..6278be2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDependencyGraph.java
@@ -0,0 +1,139 @@
+package aQute.bnd.maven;
+
+import java.net.*;
+import java.util.*;
+
+import javax.xml.parsers.*;
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+public class MavenDependencyGraph {
+	final static DocumentBuilderFactory	docFactory		= DocumentBuilderFactory.newInstance();
+	final static XPathFactory			xpathFactory	= XPathFactory.newInstance();
+	final List<Artifact>				dependencies	= new ArrayList<Artifact>();
+	final List<URL>						repositories	= new ArrayList<URL>();
+	final XPath							xpath			= xpathFactory.newXPath();
+	final Map<URL, Artifact>			cache			= new HashMap<URL, Artifact>();
+	Artifact						root;
+
+	enum Scope {
+		COMPILE, RUNTIME, TEST, PROVIDED, SYSTEM, IMPORT,
+	};
+
+	
+	public class Artifact {
+
+		String			groupId;
+		String			artifactId;
+		String			version;
+		Scope			scope			= Scope.COMPILE;
+		boolean			optional;
+		String			type;
+		URL				url;
+		List<Artifact>	dependencies	= new ArrayList<Artifact>();
+
+		public Artifact(URL url) throws Exception {
+			if (url != null) {
+				this.url = url;
+				DocumentBuilder db = docFactory.newDocumentBuilder();
+				Document doc = db.parse(url.toString());
+				Node node = (Node) xpath.evaluate("/project", doc, XPathConstants.NODE);
+
+				groupId = xpath.evaluate("groupId", node);
+				artifactId = xpath.evaluate("artifactId", node);
+				version = xpath.evaluate("version", node);
+				type = xpath.evaluate("type", node);
+				optional = (Boolean) xpath.evaluate("optinal", node, XPathConstants.BOOLEAN);
+				String scope = xpath.evaluate("scope", node);
+				if (scope != null && scope.length() > 0) {
+					this.scope = Scope.valueOf(scope.toUpperCase());
+				}
+				NodeList evaluate = (NodeList) xpath.evaluate("//dependencies/dependency", doc,
+						XPathConstants.NODESET);
+
+				for (int i = 0; i < evaluate.getLength(); i++) {
+					Node childNode = evaluate.item(i);
+					Artifact artifact = getArtifact(xpath.evaluate("groupId", childNode), xpath
+							.evaluate("artifactId", childNode), xpath.evaluate("version", childNode));
+					add(artifact);
+				}
+			}
+		}
+		
+		
+
+		public void add(Artifact artifact) {
+			dependencies.add(artifact);
+		}
+
+
+
+		public String toString() {
+			return groupId + "." + artifactId + "-" + version + "[" + scope + "," + optional + "]";
+		}
+
+		public String getPath() throws URISyntaxException {
+			return groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + artifactId
+					+ "-" + version;
+		}
+
+	}
+
+	public void addRepository(URL repository) {
+		repositories.add(repository);
+	}
+
+	/**
+	 * @param xp
+	 * @param node
+	 * @param d
+	 * @throws XPathExpressionException
+	 */
+
+	public Artifact getArtifact(String groupId, String artifactId, String version) {
+		for (URL repository : repositories) {
+			String path = getPath(repository.toString(), groupId, artifactId, version);
+
+			try {
+				URL url = new URL(path + ".pom");
+				if (cache.containsKey(url)) {
+					return cache.get(url);
+				} else {
+					return new Artifact(url);
+				}
+			} catch (Exception e) {
+				System.err.println("Failed to get " + artifactId + " from " + repository);
+			}
+		}
+		return null;
+	}
+
+	private String getPath(String path, String groupId, String artifactId, String version) {
+		StringBuilder sb = new StringBuilder();
+		sb.append(path);
+		if (!path.endsWith("/"))
+			sb.append("/");
+
+		sb.append(groupId.replace('.', '/'));
+		sb.append('/');
+		sb.append(artifactId);
+		sb.append('/');
+		sb.append(version);
+		sb.append('/');
+		sb.append(artifactId);
+		sb.append('-');
+		sb.append(version);
+		return null;
+	}
+
+	
+	
+	public void addArtifact( Artifact artifact ) throws Exception {
+		if ( root == null)
+			root = new Artifact(null);
+		root.add(artifact);
+	}
+	
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeploy.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeploy.java
new file mode 100644
index 0000000..24981ca
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeploy.java
@@ -0,0 +1,186 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.build.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.reporter.*;
+
+public class MavenDeploy implements Deploy, Plugin {
+
+	String		repository;
+	String		url;
+	String		homedir;
+	String		keyname;
+
+	String		passphrase;
+	Reporter	reporter;
+
+	public void setProperties(Map<String, String> map) {
+		repository = map.get("repository");
+		url = map.get("url");
+		passphrase = map.get("passphrase");
+		homedir = map.get("homedir");
+		keyname = map.get("keyname");
+
+		if (url == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository URL");
+		if (repository == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository name");
+	}
+
+	public void setReporter(Reporter processor) {
+		this.reporter = processor;
+	}
+
+	/**
+	 */
+	public boolean deploy(Project project, Jar original) throws Exception {
+		Map<String, Map<String, String>> deploy = project.parseHeader(project
+				.getProperty(Constants.DEPLOY));
+
+		Map<String, String> maven = deploy.get(repository);
+		if (maven == null)
+			return false; // we're not playing for this bundle
+
+		project.progress("deploying %s to Maven repo: %s", original, repository);
+		File target = project.getTarget();
+		File tmp = Processor.getFile(target, repository);
+		tmp.mkdirs();
+
+		Manifest manifest = original.getManifest();
+		if (manifest == null)
+			project.error("Jar has no manifest: %s", original);
+		else {
+			project.progress("Writing pom.xml");
+			PomResource pom = new PomResource(manifest);
+			pom.setProperties(maven);
+			File pomFile = write(tmp, pom, "pom.xml");
+
+			Jar main = new Jar("main");
+			Jar src = new Jar("src");
+			try {
+				split(original, main, src);
+				Map<String, Map<String, String>> exports = project.parseHeader(manifest
+						.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+				File jdoc = new File(tmp, "jdoc");
+				jdoc.mkdirs();
+				project.progress("Generating Javadoc for: " + exports.keySet());
+				Jar javadoc = javadoc(jdoc, project, exports.keySet());
+				project.progress("Writing javadoc jar");
+				File javadocFile = write(tmp, new JarResource(javadoc), "javadoc.jar");
+				project.progress("Writing main file");
+				File mainFile = write(tmp, new JarResource(main), "main.jar");
+				project.progress("Writing sources file");
+				File srcFile = write(tmp, new JarResource(main), "src.jar");
+
+				project.progress("Deploying main file");
+				maven_gpg_sign_and_deploy(project, mainFile, null, pomFile);
+				project.progress("Deploying main sources file");
+				maven_gpg_sign_and_deploy(project, srcFile, "sources", null);
+				project.progress("Deploying main javadoc file");
+				maven_gpg_sign_and_deploy(project, javadocFile, "javadoc", null);
+
+			} finally {
+				main.close();
+				src.close();
+			}
+		}
+		return true;
+	}
+
+	private void split(Jar original, Jar main, Jar src) {
+		for (Map.Entry<String, Resource> e : original.getResources().entrySet()) {
+			String path = e.getKey();
+			if (path.startsWith("OSGI-OPT/src/")) {
+				src.putResource(path.substring("OSGI-OPT/src/".length()), e.getValue());
+			} else {
+				main.putResource(path, e.getValue());
+			}
+		}
+	}
+
+	// gpg:sign-and-deploy-file \
+	// -Durl=http://oss.sonatype.org/service/local/staging/deploy/maven2
+	// \
+	// -DrepositoryId=sonatype-nexus-staging \
+	// -DupdateReleaseInfo=true \
+	// -DpomFile=pom.xml \
+	// -Dfile=/Ws/bnd/biz.aQute.bndlib/tmp/biz.aQute.bndlib.jar \
+	// -Dpassphrase=a1k3v3t5x3
+
+	private void maven_gpg_sign_and_deploy(Project b, File file, String classifier, File pomFile)
+			throws Exception {
+		Command command = new Command();
+		command.setTrace();
+		command.add(b.getProperty("mvn", "mvn"));
+		command.add("gpg:sign-and-deploy-file", "-DreleaseInfo=true", "-DpomFile=pom.xml");
+		command.add("-Dfile=" + file.getAbsolutePath());
+		command.add("-DrepositoryId=" + repository);
+		command.add("-Durl=" + url);
+		optional(command, "passphrase", passphrase);
+		optional(command, "keyname", keyname);
+		optional(command, "homedir", homedir);
+		optional(command, "classifier", classifier);
+		optional(command, "pomFile", pomFile == null ? null : pomFile.getAbsolutePath());
+
+		StringBuffer stdout = new StringBuffer();
+		StringBuffer stderr = new StringBuffer();
+
+		int result = command.execute(stdout, stderr);
+		if (result != 0) {
+			b.error("Maven deploy to %s failed to sign and transfer %s because %s", repository,
+					file, "" + stdout + stderr);
+		}
+	}
+
+	private void optional(Command command, String key, String value) {
+		if (value == null)
+			return;
+
+		command.add("-D=" + value);
+	}
+
+	private Jar javadoc(File tmp, Project b, Set<String> exports) throws Exception {
+		Command command = new Command();
+		
+		command.add(b.getProperty("javadoc", "javadoc"));
+		command.add("-d");
+		command.add(tmp.getAbsolutePath());
+		command.add("-sourcepath");
+		command.add( Processor.join(b.getSourcePath(),File.pathSeparator));
+
+		for (String packageName : exports) {
+			command.add(packageName);
+		}
+
+		StringBuffer out = new StringBuffer();
+		StringBuffer err = new StringBuffer();
+		Command c = new Command();
+		c.setTrace();
+		int result = c.execute(out, err);
+		if (result == 0) {
+			Jar jar = new Jar(tmp);
+			b.addClose(jar);
+			return jar;
+		}
+		b.error("Error during execution of javadoc command: %s / %s", out, err);
+		return null;
+	}
+
+	private File write(File base, Resource r, String fileName) throws Exception {
+		File f = Processor.getFile(base, fileName);
+		OutputStream out = new FileOutputStream(f);
+		try {
+			r.write(out);
+		} finally {
+			out.close();
+		}
+		return f;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeployCmd.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeployCmd.java
new file mode 100644
index 0000000..aaa23ff
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeployCmd.java
@@ -0,0 +1,221 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.build.*;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.reporter.*;
+
+public class MavenDeployCmd extends Processor {
+
+	String		repository	= "nexus";
+	String		url			= "http://oss.sonatype.org/service/local/staging/deploy/maven2";
+	String		homedir;
+	String		keyname;
+
+	String		passphrase;
+	Reporter	reporter;
+
+	/**
+	 * maven deploy [-url repo] [-passphrase passphrase] [-homedir homedir]
+	 * [-keyname keyname] bundle ...
+	 * 
+	 * @param args
+	 * @param i
+	 * @throws Exception
+	 */
+	void run(String args[], int i) throws Exception {
+		if (i >= args.length) {
+			System.err
+					.println("Usage:\n");
+			System.err.println("  deploy [-url repo] [-passphrase passphrase] [-homedir homedir] [-keyname keyname] bundle ...");
+			System.err.println("  settings");
+			return;
+		}
+
+		@SuppressWarnings("unused") String cmd = args[i++];
+		
+		while (i < args.length && args[i].startsWith("-")) {
+			String option = args[i];
+			if (option.equals("-url"))
+				repository = args[++i];
+			else if (option.equals("-passphrase"))
+				passphrase = args[++i];
+			else if (option.equals("-url"))
+				homedir = args[++i];
+			else if (option.equals("-keyname"))
+				keyname = args[++i];
+			else
+				error("Invalid command ");
+		}
+
+		
+	}
+
+	public void setProperties(Map<String, String> map) {
+		repository = map.get("repository");
+		url = map.get("url");
+		passphrase = map.get("passphrase");
+		homedir = map.get("homedir");
+		keyname = map.get("keyname");
+
+		if (url == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository URL");
+		if (repository == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository name");
+	}
+
+	public void setReporter(Reporter processor) {
+		this.reporter = processor;
+	}
+
+	/**
+	 */
+	public boolean deploy(Project project, Jar original) throws Exception {
+		Map<String, Map<String, String>> deploy = project.parseHeader(project
+				.getProperty(Constants.DEPLOY));
+
+		Map<String, String> maven = deploy.get(repository);
+		if (maven == null)
+			return false; // we're not playing for this bundle
+
+		project.progress("deploying %s to Maven repo: %s", original, repository);
+		File target = project.getTarget();
+		File tmp = Processor.getFile(target, repository);
+		tmp.mkdirs();
+
+		Manifest manifest = original.getManifest();
+		if (manifest == null)
+			project.error("Jar has no manifest: %s", original);
+		else {
+			project.progress("Writing pom.xml");
+			PomResource pom = new PomResource(manifest);
+			pom.setProperties(maven);
+			File pomFile = write(tmp, pom, "pom.xml");
+
+			Jar main = new Jar("main");
+			Jar src = new Jar("src");
+			try {
+				split(original, main, src);
+				Map<String, Map<String, String>> exports = project.parseHeader(manifest
+						.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+				File jdoc = new File(tmp, "jdoc");
+				jdoc.mkdirs();
+				project.progress("Generating Javadoc for: " + exports.keySet());
+				Jar javadoc = javadoc(jdoc, project, exports.keySet());
+				project.progress("Writing javadoc jar");
+				File javadocFile = write(tmp, new JarResource(javadoc), "javadoc.jar");
+				project.progress("Writing main file");
+				File mainFile = write(tmp, new JarResource(main), "main.jar");
+				project.progress("Writing sources file");
+				File srcFile = write(tmp, new JarResource(main), "src.jar");
+
+				project.progress("Deploying main file");
+				maven_gpg_sign_and_deploy(project, mainFile, null, pomFile);
+				project.progress("Deploying main sources file");
+				maven_gpg_sign_and_deploy(project, srcFile, "sources", null);
+				project.progress("Deploying main javadoc file");
+				maven_gpg_sign_and_deploy(project, javadocFile, "javadoc", null);
+
+			} finally {
+				main.close();
+				src.close();
+			}
+		}
+		return true;
+	}
+
+	private void split(Jar original, Jar main, Jar src) {
+		for (Map.Entry<String, Resource> e : original.getResources().entrySet()) {
+			String path = e.getKey();
+			if (path.startsWith("OSGI-OPT/src/")) {
+				src.putResource(path.substring("OSGI-OPT/src/".length()), e.getValue());
+			} else {
+				main.putResource(path, e.getValue());
+			}
+		}
+	}
+
+	// gpg:sign-and-deploy-file \
+	// -Durl=http://oss.sonatype.org/service/local/staging/deploy/maven2
+	// \
+	// -DrepositoryId=sonatype-nexus-staging \
+	// -DupdateReleaseInfo=true \
+	// -DpomFile=pom.xml \
+	// -Dfile=/Ws/bnd/biz.aQute.bndlib/tmp/biz.aQute.bndlib.jar \
+	// -Dpassphrase=a1k3v3t5x3
+
+	private void maven_gpg_sign_and_deploy(Project b, File file, String classifier, File pomFile)
+			throws Exception {
+		Command command = new Command();
+		command.setTrace();
+		command.add(b.getProperty("mvn", "mvn"));
+		command.add("gpg:sign-and-deploy-file", "-DreleaseInfo=true", "-DpomFile=pom.xml");
+		command.add("-Dfile=" + file.getAbsolutePath());
+		command.add("-DrepositoryId=" + repository);
+		command.add("-Durl=" + url);
+		optional(command, "passphrase", passphrase);
+		optional(command, "keyname", keyname);
+		optional(command, "homedir", homedir);
+		optional(command, "classifier", classifier);
+		optional(command, "pomFile", pomFile == null ? null : pomFile.getAbsolutePath());
+
+		StringBuffer stdout = new StringBuffer();
+		StringBuffer stderr = new StringBuffer();
+
+		int result = command.execute(stdout, stderr);
+		if (result != 0) {
+			b.error("Maven deploy to %s failed to sign and transfer %s because %s", repository,
+					file, "" + stdout + stderr);
+		}
+	}
+
+	private void optional(Command command, String key, String value) {
+		if (value == null)
+			return;
+
+		command.add("-D=" + value);
+	}
+
+	private Jar javadoc(File tmp, Project b, Set<String> exports) throws Exception {
+		Command command = new Command();
+
+		command.add(b.getProperty("javadoc", "javadoc"));
+		command.add("-d");
+		command.add(tmp.getAbsolutePath());
+		command.add("-sourcepath");
+		command.add(Processor.join(b.getSourcePath(), File.pathSeparator));
+
+		for (String packageName : exports) {
+			command.add(packageName);
+		}
+
+		StringBuffer out = new StringBuffer();
+		StringBuffer err = new StringBuffer();
+		Command c = new Command();
+		c.setTrace();
+		int result = c.execute(out, err);
+		if (result == 0) {
+			Jar jar = new Jar(tmp);
+			b.addClose(jar);
+			return jar;
+		}
+		b.error("Error during execution of javadoc command: %s / %s", out, err);
+		return null;
+	}
+
+	private File write(File base, Resource r, String fileName) throws Exception {
+		File f = Processor.getFile(base, fileName);
+		OutputStream out = new FileOutputStream(f);
+		try {
+			r.write(out);
+		} finally {
+			out.close();
+		}
+		return f;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenGroup.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenGroup.java
new file mode 100644
index 0000000..2d49be6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenGroup.java
@@ -0,0 +1,27 @@
+package aQute.bnd.maven;
+
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.libg.reporter.*;
+
+public class MavenGroup implements BsnToMavenPath, Plugin {
+    String    groupId = "";
+
+    public String[] getGroupAndArtifact(String bsn) {
+        String[] result = new String[2];
+        result[0] = groupId;
+        result[1] = bsn;
+        return result;
+    }
+
+    public void setProperties(Map<String, String> map) {
+        if (map.containsKey("groupId")) {
+            groupId = map.get("groupId");
+        }
+    }
+
+    public void setReporter(Reporter processor) {
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
new file mode 100644
index 0000000..506ea53
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
@@ -0,0 +1,194 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class MavenRepository implements RepositoryPlugin, Plugin, BsnToMavenPath {
+
+	public static String	NAME	= "name";
+
+	File					root;
+	Reporter				reporter;
+	String					name;
+
+	public String toString() {
+		return "maven:" + root;
+	}
+
+	public boolean canWrite() {
+		return false;
+	}
+
+	public File[] get(String bsn, String version) throws Exception {
+		VersionRange range = new VersionRange("0");
+		if (version != null)
+			range = new VersionRange(version);
+
+		List<BsnToMavenPath> plugins = ((Processor) reporter).getPlugins(BsnToMavenPath.class);
+		if ( plugins.isEmpty())
+			plugins.add(this);
+		
+		for (BsnToMavenPath cvr : plugins) {
+			String[] paths = cvr.getGroupAndArtifact(bsn);
+			if (paths != null) {
+				File[] files = find(paths[0], paths[1], range);
+				if (files != null)
+					return files;
+			}
+		}
+		reporter.trace("Cannot find in maven: %s-%s", bsn, version);
+		return null;
+	}
+
+	File[] find(String groupId, String artifactId, VersionRange range) {
+		String path = groupId.replace(".", "/");
+		File vsdir = Processor.getFile(root, path);
+		if (!vsdir.isDirectory())
+			return null;
+
+		vsdir = Processor.getFile(vsdir, artifactId);
+
+		List<File> result = new ArrayList<File>();
+		if (vsdir.isDirectory()) {
+			String versions[] = vsdir.list();
+			for (String v : versions) {
+				String vv = Analyzer.cleanupVersion(v);
+				if (Verifier.isVersion(vv)) {
+					Version vvv = new Version(vv);
+					if (range.includes(vvv)) {
+						File file = Processor.getFile(vsdir, v + "/" + artifactId + "-" + v
+								+ ".jar");
+						if (file.isFile())
+							result.add(file);
+						else
+							reporter.warning("Expected maven entry was not a valid file %s ", file);
+					}
+				} else {
+					reporter
+							.warning(
+									"Expected a version directory in maven: dir=%s raw-version=%s cleaned-up-version=%s",
+									vsdir, vv, v);
+				}
+			}
+		} else
+			return null;
+
+		return result.toArray(new File[result.size()]);
+	}
+
+	public List<String> list(String regex) {
+		List<String> bsns = new ArrayList<String>();
+		Pattern match = Pattern.compile(".*");
+		if (regex != null)
+			match = Pattern.compile(regex);
+		find(bsns, match, root, "");
+		return bsns;
+	}
+
+	void find(List<String> bsns, Pattern pattern, File base, String name) {
+		if (base.isDirectory()) {
+			String list[] = base.list();
+			boolean found = false;
+			for (String entry : list) {
+				char c = entry.charAt(0);
+				if (c >= '0' && c <= '9') {
+					if (pattern.matcher(name).matches())
+						found = true;
+				} else {
+					String nextName = entry;
+					if (name.length() != 0)
+						nextName = name + "." + entry;
+
+					File next = Processor.getFile(base, entry);
+					find(bsns, pattern, next, nextName);
+				}
+			}
+			if (found)
+				bsns.add(name);
+		}
+	}
+
+	public File put(Jar jar) throws Exception {
+		throw new IllegalStateException("Maven does not support the put command");
+	}
+
+	public List<Version> versions(String bsn) throws Exception {
+		
+		File files[] = get( bsn, null);
+		List<Version> versions = new ArrayList<Version>();
+		for ( File f : files ) {
+			Version v = new Version( f.getParentFile().getName());
+			versions.add(v);
+		}
+		return versions;
+	}
+
+	public void setProperties(Map<String, String> map) {
+		File home = new File("");
+		String root = map.get("root");
+		if (root == null) {
+			home = new File( System.getProperty("user.home") );
+			this.root = Processor.getFile(home , ".m2/repository").getAbsoluteFile();
+		} else
+			this.root = Processor.getFile(home, root).getAbsoluteFile();
+		
+		if (!this.root.isDirectory()) {
+			reporter.error("Maven repository did not get a proper URL to the repository %s", root);
+		}
+		name = (String) map.get(NAME);
+
+	}
+
+	public void setReporter(Reporter processor) {
+		this.reporter = processor;
+	}
+
+	public String[] getGroupAndArtifact(String bsn) {
+		String groupId;
+		String artifactId;
+		int n = bsn.indexOf('.');
+		
+		while ( n > 0 ) {
+			artifactId = bsn.substring(n+1);
+			groupId = bsn.substring(0,n);
+			
+			File gdir = new File(root, groupId.replace('.',File.separatorChar)).getAbsoluteFile();
+			File adir = new File( gdir, artifactId).getAbsoluteFile();
+			if ( adir.isDirectory() )
+				return new String[] {groupId, artifactId};
+			
+			n = bsn.indexOf('.',n+1);
+		}
+		return null;
+	}
+	
+	public String getName() {
+		if (name == null) {
+			return toString();
+		}
+		return name;
+	}
+
+	public File get(String bsn, String range, Strategy strategy, Map<String,String> properties) throws Exception {
+		File[] files = get(bsn, range);
+		if (files.length >= 0) {
+			switch (strategy) {
+			case LOWEST:
+				return files[0];
+			case HIGHEST:
+				return files[files.length - 1];
+			}
+		}
+		return null;
+	}
+
+	public void setRoot( File f  ) {
+		root = f;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/PomFromManifest.java b/bundleplugin/src/main/java/aQute/bnd/maven/PomFromManifest.java
new file mode 100644
index 0000000..3ed560d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomFromManifest.java
@@ -0,0 +1,247 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+public class PomFromManifest extends WriteResource {
+	final Manifest			manifest;
+	private List<String>	scm			= new ArrayList<String>();
+	private List<String>	developers	= new ArrayList<String>();
+	final static Pattern	NAME_URL	= Pattern.compile("(.*)(http://.*)");
+	String					xbsn;
+	String					xversion;
+	String					xgroupId;
+	String					xartifactId;
+	private String			projectURL;
+
+	public String getBsn() {
+		if (xbsn == null)
+			xbsn = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+		if (xbsn == null)
+			throw new RuntimeException("Cannot create POM unless bsn is set");
+
+		xbsn = xbsn.trim();
+		int n = xbsn.lastIndexOf('.');
+		if (n < 0) {
+			n = xbsn.length();
+			xbsn = xbsn + "." + xbsn;
+		}
+
+		if (xgroupId == null)
+			xgroupId = xbsn.substring(0, n);
+		if (xartifactId == null) {
+			xartifactId = xbsn.substring(n + 1);
+			n = xartifactId.indexOf(';');
+			if (n > 0)
+				xartifactId = xartifactId.substring(0, n).trim();
+		}
+
+		return xbsn;
+	}
+
+	public String getGroupId() {
+		getBsn();
+		return xgroupId;
+	}
+
+	public String getArtifactId() {
+		getBsn();
+		return xartifactId;
+	}
+
+	public Version getVersion() {
+		if (xversion != null)
+			return new Version(xversion);
+		String version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+		Version v = new Version(version);
+		return new Version(v.getMajor(), v.getMinor(), v.getMicro());
+	}
+
+	public PomFromManifest(Manifest manifest) {
+		this.manifest = manifest;
+	}
+
+	@Override public long lastModified() {
+		return 0;
+	}
+
+	@Override public void write(OutputStream out) throws IOException {
+		PrintWriter ps = new PrintWriter(out);
+
+		String name = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_NAME);
+
+		String description = manifest.getMainAttributes().getValue(Constants.BUNDLE_DESCRIPTION);
+		String docUrl = manifest.getMainAttributes().getValue(Constants.BUNDLE_DOCURL);
+		String bundleVendor = manifest.getMainAttributes().getValue(Constants.BUNDLE_VENDOR);
+
+		String licenses = manifest.getMainAttributes().getValue(Constants.BUNDLE_LICENSE);
+
+		Tag project = new Tag("project");
+		project.addAttribute("xmlns", "http://maven.apache.org/POM/4.0.0");
+		project.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+		project.addAttribute("xsi:schemaLocation",
+				"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd");
+
+		project.addContent(new Tag("modelVersion").addContent("4.0.0"));
+		project.addContent(new Tag("groupId").addContent(getGroupId()));
+		project.addContent(new Tag("artifactId").addContent(getArtifactId()));
+		project.addContent(new Tag("version").addContent(getVersion().toString()));
+
+		if (description != null) {
+			new Tag(project, "description").addContent(description);
+		}
+		if (name != null) {
+			new Tag(project, "name").addContent(name);
+		}
+
+		if (projectURL != null)
+			new Tag(project, "url").addContent(projectURL);
+		else if (docUrl != null) {
+			new Tag(project, "url").addContent(docUrl);
+		} else
+			new Tag(project, "url").addContent("http://no-url");
+
+		String scmheader = manifest.getMainAttributes().getValue("Bundle-SCM");
+		if (scmheader != null)
+			scm.add(scmheader);
+
+		Tag scmtag = new Tag(project, "scm");
+		if (scm != null && !scm.isEmpty()) {
+			for (String cm : this.scm) {
+				new Tag(scmtag, "url").addContent(cm);
+				new Tag(scmtag, "connection").addContent(cm);
+				new Tag(scmtag, "developerConnection").addContent(cm);
+			}
+		} else {
+			new Tag(scmtag, "url").addContent("private");
+			new Tag(scmtag, "connection").addContent("private");
+			new Tag(scmtag, "developerConnection").addContent("private");
+		}
+
+		if (bundleVendor != null) {
+			Matcher m = NAME_URL.matcher(bundleVendor);
+			String namePart = bundleVendor;
+			String urlPart = this.projectURL;
+			if (m.matches()) {
+				namePart = m.group(1);
+				urlPart = m.group(2);
+			}
+			Tag organization = new Tag(project, "organization");
+			new Tag(organization, "name").addContent(namePart.trim());
+			if (urlPart != null) {
+				new Tag(organization, "url").addContent(urlPart.trim());
+			}
+		}
+		if (!developers.isEmpty()) {
+			Tag d = new Tag(project, "developers");
+			for (String email : developers) {
+				String id = email;
+				String xname = email;
+				String organization = null;
+
+				Matcher m = Pattern.compile("([^@]+)@([\\d\\w\\-_\\.]+)\\.([\\d\\w\\-_\\.]+)")
+						.matcher(email);
+				if (m.matches()) {
+					xname = m.group(1);
+					organization = m.group(2);
+				}
+
+				Tag developer = new Tag(d, "developer");
+				new Tag(developer, "id").addContent(id);
+				new Tag(developer, "name").addContent(xname);
+				new Tag(developer, "email").addContent(email);
+				if (organization != null)
+					new Tag(developer, "organization").addContent(organization);
+			}
+		}
+		if (licenses != null) {
+			Tag ls = new Tag(project, "licenses");
+
+			Map<String, Map<String, String>> map = Processor.parseHeader(licenses, null);
+			for (Iterator<Map.Entry<String, Map<String, String>>> e = map.entrySet().iterator(); e
+					.hasNext();) {
+
+				// Bundle-License:
+				// http://www.opensource.org/licenses/apache2.0.php; \
+				// description="${Bundle-Copyright}"; \
+				// link=LICENSE
+				//
+				//  <license>
+				//    <name>This material is licensed under the Apache
+				// Software License, Version 2.0</name>
+				//    <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+				//    <distribution>repo</distribution>
+				//    </license>
+
+				Map.Entry<String, Map<String, String>> entry = e.next();
+				Tag l = new Tag(ls, "license");
+				Map<String, String> values = entry.getValue();
+				String url = entry.getKey();
+
+				if (values.containsKey("description"))
+					tagFromMap(l, values, "description", "name", url);
+				else
+					tagFromMap(l, values, "name", "name", url);
+
+				tagFromMap(l, values, "url", "url", url);
+				tagFromMap(l, values, "distribution", "distribution", "repo");
+			}
+		}
+		project.print(0, ps);
+		ps.flush();
+	}
+
+	/**
+	 * Utility function to print a tag from a map
+	 * 
+	 * @param ps
+	 * @param values
+	 * @param string
+	 * @param tag
+	 * @param object
+	 */
+	private Tag tagFromMap(Tag parent, Map<String, String> values, String string, String tag,
+			String object) {
+		String value = (String) values.get(string);
+		if (value == null)
+			value = object;
+		if (value == null)
+			return parent;
+		new Tag(parent, tag).addContent(value.trim());
+		return parent;
+	}
+
+	public void setSCM(String scm) {
+		this.scm.add(scm);
+	}
+
+	public void setURL(String url) {
+		this.projectURL = url;
+	}
+
+	public void setBsn(String bsn) {
+		this.xbsn = bsn;
+	}
+
+	public void addDeveloper(String email) {
+		this.developers.add(email);
+	}
+
+	public void setVersion(String version) {
+		this.xversion = version;
+	}
+
+	public void setArtifact(String artifact) {
+		this.xartifactId = artifact;
+	}
+
+	public void setGroup(String group) {
+		this.xgroupId = group;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/PomParser.java b/bundleplugin/src/main/java/aQute/bnd/maven/PomParser.java
new file mode 100644
index 0000000..e529141
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomParser.java
@@ -0,0 +1,144 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+
+import javax.xml.parsers.*;
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+
+/**
+ * Provides a way to parse a maven pom as properties.
+ * 
+ * This provides most of the maven elements as properties. It also
+ * provides pom.scope.[compile|test|runtime|provided|system] properties
+ * that can be appended to the build and run path. That is, they are
+ * in the correct format for this.
+ */
+public class PomParser extends Processor {
+	static DocumentBuilderFactory	dbf			= DocumentBuilderFactory.newInstance();
+	static XPathFactory				xpathf		= XPathFactory.newInstance();
+	static Set<String>				multiple	= new HashSet<String>();
+	static Set<String>				skip	= new HashSet<String>();
+
+	static {
+		dbf.setNamespaceAware(false);
+		
+		// Set all elements that need enumeration of their elements
+		// these will not use the name of the subelement but instead
+		// they use an index from 0..n
+		multiple.add("mailingLists");
+		multiple.add("pluginRepositories");
+		multiple.add("repositories");
+		multiple.add("resources");
+		multiple.add("executions");
+		multiple.add("goals");
+		multiple.add("includes");
+		multiple.add("excludes");
+
+		// These properties are not very useful and
+		// pollute the property space.
+		skip.add("plugins");
+		skip.add("dependencies");
+		skip.add("reporting");
+		skip.add("extensions");
+		
+	}
+
+	public Properties getProperties(File pom) throws Exception {
+		DocumentBuilder db = dbf.newDocumentBuilder();
+		XPath xpath = xpathf.newXPath();
+		pom = pom.getAbsoluteFile();
+		Document doc = db.parse(pom);
+		Properties p = new Properties();
+
+		// Check if there is a parent pom
+		String relativePath = xpath.evaluate("project/parent/relativePath", doc);
+		if (relativePath != null && relativePath.length()!=0) {
+			File parentPom = IO.getFile(pom.getParentFile(), relativePath);
+			if (parentPom.isFile()) {
+				Properties parentProps = getProperties(parentPom);
+				p.putAll(parentProps);
+			} else {
+				error("Parent pom for %s is not an existing file (could be directory): %s", pom, parentPom);
+			}
+		}
+
+		Element e = doc.getDocumentElement();
+		traverse("pom", e, p);
+
+		String scopes[] = { "provided", "runtime", "test", "system" };
+		NodeList set = (NodeList) xpath.evaluate("//dependency[not(scope) or scope='compile']", doc,
+				XPathConstants.NODESET);
+		if (set.getLength() != 0)
+			p.put("pom.scope.compile", toBsn(set));
+
+		for (String scope : scopes) {
+			set = (NodeList) xpath.evaluate("//dependency[scope='" + scope + "']", doc,
+					XPathConstants.NODESET);
+			if (set.getLength() != 0)
+				p.put("pom.scope." + scope, toBsn(set));
+		}
+
+		return p;
+	}
+
+	private Object toBsn(NodeList set) throws XPathExpressionException {
+		XPath xpath = xpathf.newXPath();
+		StringBuilder sb = new StringBuilder();
+		String del = "";
+		for (int i = 0; i < set.getLength(); i++) {
+			Node child = set.item(i);
+			String version = xpath.evaluate("version", child);
+			sb.append(del);
+			sb.append(xpath.evaluate("groupId", child));
+			sb.append(".");
+			sb.append(xpath.evaluate("artifactId", child));
+			if (version != null && version.trim().length()!=0) {
+				sb.append(";version=");
+				sb.append( Analyzer.cleanupVersion(version));
+			}
+			del = ", ";
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * The maven POM is quite straightforward, it is basically a structured property file.
+	 * @param name
+	 * @param parent
+	 * @param p
+	 */
+	static void traverse(String name, Node parent, Properties p) {
+		if ( skip.contains(parent.getNodeName()))
+			return;
+		
+		NodeList children = parent.getChildNodes();
+		if (multiple.contains(parent.getNodeName())) {
+			int n = 0;
+			for (int i = 0; i < children.getLength(); i++) {
+				Node child = children.item(i);
+				if (!(child instanceof Text)) {
+
+					traverse(name + "." + n++, child, p);
+				}
+			}
+		} else {
+			for (int i = 0; i < children.getLength(); i++) {
+				Node child = children.item(i);
+				if (child instanceof Text) {
+					String value = child.getNodeValue().trim();
+					if (value.length()!=0) {
+						p.put(name, value);
+					}
+				} else {
+					traverse(name + "." + child.getNodeName(), child, p);
+				}
+			}
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/PomResource.java b/bundleplugin/src/main/java/aQute/bnd/maven/PomResource.java
new file mode 100644
index 0000000..e5e6b8d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomResource.java
@@ -0,0 +1,159 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+
+public class PomResource extends WriteResource {
+	final Manifest				manifest;
+	private Map<String, String>	scm;
+	final static Pattern		NAME_URL	= Pattern.compile("(.*)(http://.*)");
+
+	public PomResource(Manifest manifest) {
+		this.manifest = manifest;
+	}
+
+	@Override public long lastModified() {
+		return 0;
+	}
+
+	@Override public void write(OutputStream out) throws IOException {
+		PrintWriter ps = new PrintWriter(out);
+
+		String name = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_NAME);
+
+		String description = manifest.getMainAttributes().getValue(Constants.BUNDLE_DESCRIPTION);
+		String docUrl = manifest.getMainAttributes().getValue(Constants.BUNDLE_DOCURL);
+		String version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+		String bundleVendor = manifest.getMainAttributes().getValue(Constants.BUNDLE_VENDOR);
+
+		String bsn = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+		String licenses = manifest.getMainAttributes().getValue(Constants.BUNDLE_LICENSE);
+
+		if (bsn == null) {
+			throw new RuntimeException("Cannot create POM unless bsn is set");
+		}
+
+		bsn = bsn.trim();
+		int n = bsn.lastIndexOf('.');
+		if (n <= 0)
+			throw new RuntimeException(
+					"Can not create POM unless Bundle-SymbolicName contains a . to separate group and  artifact id");
+
+		if (version == null)
+			version = "0";
+
+		String groupId = bsn.substring(0, n);
+		String artifactId = bsn.substring(n + 1);
+		n = artifactId.indexOf(';');
+		if (n > 0)
+			artifactId = artifactId.substring(0, n).trim();
+
+		Tag project = new Tag("project");
+		project.addAttribute("xmlns", "http://maven.apache.org/POM/4.0.0");
+		project.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+		project.addAttribute("xmlns:xsi", "");
+		project.addAttribute("xsi:schemaLocation",
+				"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd");
+
+		project.addContent(new Tag("modelVersion").addContent("4.0.0"));
+		project.addContent(new Tag("groupId").addContent(groupId));
+		project.addContent(new Tag("artifactId").addContent(artifactId));
+		project.addContent(new Tag("version").addContent(version));
+
+		if (description != null) {
+			new Tag(project, "description").addContent(description);
+		}
+		if (name != null) {
+			new Tag(project, "name").addContent(name);
+		}
+		if (docUrl != null) {
+			new Tag(project, "url").addContent(docUrl);
+		}
+
+		if (scm != null) {
+			Tag scm = new Tag(project, "scm");
+			for (Map.Entry<String, String> e : this.scm.entrySet()) {
+				new Tag(scm, e.getKey()).addContent(e.getValue());
+			}
+		}
+
+		if (bundleVendor != null) {
+			Matcher m = NAME_URL.matcher(bundleVendor);
+			String namePart = bundleVendor;
+			String urlPart = null;
+			if (m.matches()) {
+				namePart = m.group(1);
+				urlPart = m.group(2);
+			}
+			Tag organization = new Tag(project, "organization");
+			new Tag(organization, "name").addContent(namePart.trim());
+			if (urlPart != null) {
+				new Tag(organization, "url").addContent(urlPart.trim());
+			}
+		}
+		if (licenses != null) {
+			Tag ls = new Tag(project, "licenses");
+
+			Map<String, Map<String, String>> map = Processor.parseHeader(licenses, null);
+			for (Iterator<Map.Entry<String, Map<String, String>>> e = map.entrySet().iterator(); e
+					.hasNext();) {
+
+				// Bundle-License:
+				// http://www.opensource.org/licenses/apache2.0.php; \
+				// description="${Bundle-Copyright}"; \
+				// link=LICENSE
+				//
+				//  <license>
+				//    <name>This material is licensed under the Apache
+				// Software License, Version 2.0</name>
+				//    <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+				//    <distribution>repo</distribution>
+				//    </license>
+
+				Map.Entry<String, Map<String, String>> entry = e.next();
+				Tag l = new Tag(ls, "license");
+				Map<String, String> values = entry.getValue();
+				String url = entry.getKey();
+
+				if (values.containsKey("description"))
+					tagFromMap(l, values, "description", "name", url);
+				else
+					tagFromMap(l, values, "name", "name", url);
+
+				tagFromMap(l, values, "url", "url", url);
+				tagFromMap(l, values, "distribution", "distribution", "repo");
+			}
+		}
+		project.print(0, ps);
+		ps.flush();
+	}
+
+	/**
+	 * Utility function to print a tag from a map
+	 * 
+	 * @param ps
+	 * @param values
+	 * @param string
+	 * @param tag
+	 * @param object
+	 */
+	private Tag tagFromMap(Tag parent, Map<String, String> values, String string, String tag,
+			String object) {
+		String value = (String) values.get(string);
+		if (value == null)
+			value = object;
+		if (value == null)
+			return parent;
+		new Tag(parent, tag).addContent(value.trim());
+		return parent;
+	}
+
+	public void setProperties(Map<String, String> scm) {
+		this.scm = scm;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/CachedPom.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/CachedPom.java
new file mode 100644
index 0000000..02449fa
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/CachedPom.java
@@ -0,0 +1,18 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+
+public class CachedPom extends Pom {
+	final MavenEntry				maven;
+
+	CachedPom(MavenEntry mavenEntry, URI repo) throws Exception {
+		super(mavenEntry.maven, mavenEntry.getPomFile(), repo);
+		this.maven = mavenEntry;
+	}
+
+	public File getArtifact() throws Exception {
+		return maven.getArtifact();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/Maven.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/Maven.java
new file mode 100644
index 0000000..2124a05
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/Maven.java
@@ -0,0 +1,88 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.regex.*;
+
+/*
+ http://repository.springsource.com/maven/bundles/external/org/apache/coyote/com.springsource.org.apache.coyote/6.0.24/com.springsource.org.apache.coyote-6.0.24.pom
+ http://repository.springsource.com/maven/bundles/external/org/apache/coyote/com.springsource.org.apache.coyote/6.0.24/com.springsource.org.apache.coyote-6.0.24.pom
+ */
+public class Maven {
+	final File						userHome	= new File(System.getProperty("user.home"));
+	final Map<String, MavenEntry>	entries		= new ConcurrentHashMap<String, MavenEntry>();
+	final static String[]			ALGORITHMS	= { "md5", "sha1" };
+	boolean							usecache	= false;
+	final Executor					executor;
+	File							m2			= new File(userHome, ".m2");
+	File							repository	= new File(m2, "repository");
+
+	public Maven(Executor executor) {
+		if ( executor == null)
+			this.executor = Executors.newCachedThreadPool();
+		else
+			this.executor = executor;
+	}
+	
+	//http://repo1.maven.org/maven2/junit/junit/maven-metadata.xml
+	
+	static Pattern MAVEN_RANGE = Pattern.compile("(\\[|\\()(.+)(,(.+))(\\]|\\))");
+	public CachedPom getPom(String groupId, String artifactId, String version, URI... extra)
+			throws Exception {		
+		MavenEntry entry = getEntry(groupId, artifactId, version);
+		return entry.getPom(extra);
+	}
+
+	/**
+	 * @param groupId
+	 * @param artifactId
+	 * @param version
+	 * @param extra
+	 * @return
+	 * @throws Exception
+	 */
+	public MavenEntry getEntry(String groupId, String artifactId, String version) throws Exception {
+		String path = path(groupId, artifactId, version);
+
+		MavenEntry entry;
+		synchronized (entries) {
+			entry = entries.get(path);
+			if (entry != null)
+				return entry;
+
+			entry = new MavenEntry(this, path);
+			entries.put(path, entry);
+		}
+		return entry;
+	}
+
+	private String path(String groupId, String artifactId, String version) {
+		return groupId.replace('.', '/') + '/' + artifactId + '/' + version + "/" + artifactId
+				+ "-" + version;
+	}
+
+	public void schedule(Runnable runnable) {
+		if (executor == null)
+			runnable.run();
+		else
+			executor.execute(runnable);
+	}
+
+	public ProjectPom createProjectModel(File file) throws Exception {
+		ProjectPom pom = new ProjectPom(this, file);
+		pom.parse();
+		return pom;
+	}
+
+	public MavenEntry getEntry(Pom pom) throws Exception {
+		return getEntry(pom.getGroupId(), pom.getArtifactId(), pom.getVersion());
+	}
+
+	public void setM2(File dir) {
+		this.m2 = dir;
+		this.repository = new File(dir,"repository");
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenEntry.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenEntry.java
new file mode 100644
index 0000000..f23ba82
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenEntry.java
@@ -0,0 +1,338 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import aQute.lib.hex.*;
+import aQute.lib.io.*;
+import aQute.libg.filelock.*;
+
+/**
+ * An entry (a group/artifact) in the maven cache in the .m2/repository
+ * directory. It provides methods to get the pom and the artifact.
+ * 
+ */
+public class MavenEntry implements Closeable {
+	final Maven					maven;
+	final File					root;
+	final File					dir;
+	final String				path;
+	final DirectoryLock			lock;
+	final Map<URI, CachedPom>	poms	= new HashMap<URI, CachedPom>();
+	final File					pomFile;
+	final File					artifactFile;
+	final String				pomPath;
+	final File					propertiesFile;
+	Properties					properties;
+	private boolean				propertiesChanged;
+	FutureTask<File>			artifact;
+	private String				artifactPath;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param maven
+	 * @param path
+	 */
+	MavenEntry(Maven maven, String path) {
+		this.root = maven.repository;
+		this.maven = maven;
+		this.path = path;
+		this.pomPath = path + ".pom";
+		this.artifactPath = path + ".jar";
+		this.dir = IO.getFile(maven.repository, path).getParentFile();
+		this.dir.mkdirs();
+		this.pomFile = new File(maven.repository, pomPath);
+		this.artifactFile = new File(maven.repository, artifactPath);
+		this.propertiesFile = new File(dir, "bnd.properties");
+		this.lock = new DirectoryLock(dir, 5 * 60000); // 5 mins
+	}
+
+	/**
+	 * This is the method to get the POM for a cached entry.
+	 * 
+	 * @param urls
+	 *            The allowed URLs
+	 * @return a CachedPom for this maven entry
+	 * 
+	 * @throws Exception
+	 *             If something goes haywire
+	 */
+	public CachedPom getPom(URI[] urls) throws Exception {
+
+		// First check if we have the pom cached in memory
+		synchronized (this) {
+			// Try to find it in the in-memory cache
+			for (URI url : urls) {
+				CachedPom pom = poms.get(url);
+				if (pom != null)
+					return pom;
+			}
+		}
+
+		// Ok, we need to see if it exists on disk
+
+		// lock.lock();
+		try {
+
+			if (isValid()) {
+				// Check if one of our repos had the correct file.
+				for (URI url : urls) {
+					String valid = getProperty(url.toASCIIString());
+					if (valid != null)
+						return createPom(url);
+				}
+
+				// we have the info, but have to verify that it
+				// exists in one of our repos but we do not have
+				// to download it as our cache is already ok.
+				for (URI url : urls) {
+					if (verify(url, pomPath)) {
+						return createPom(url);
+					}
+				}
+
+				// It does not exist in out repo
+				// so we have to fail even though we do have
+				// the file.
+
+			} else {
+				dir.mkdirs();
+				// We really do not have the file
+				// so we have to find out who has it.
+				for (final URI url : urls) {
+
+					if (download(url, pomPath)) {
+						if (verify(url, pomPath)) {
+							artifact = new FutureTask<File>(new Callable<File>() {
+
+								public File call() throws Exception {
+									if (download(url, artifactPath)) {
+										verify(url, artifactPath);
+									}
+									return artifactFile;
+								}
+
+							});
+							maven.executor.execute(artifact);
+							return createPom(url);
+						}
+					}
+				}
+			}
+			return null;
+		} finally {
+			saveProperties();
+			// lock.release();
+		}
+	}
+
+	/**
+	 * Download a resource from the given repo.
+	 * 
+	 * @param url
+	 *            The base url for the repo
+	 * @param path
+	 *            The path part
+	 * @return
+	 * @throws MalformedURLException
+	 */
+	private boolean download(URI repo, String path) throws MalformedURLException {
+		try {
+			URL url = toURL(repo, path);
+			System.out.println("Downloading "  + repo + " path " + path + " url " + url);
+			File file = new File(root, path);
+			IO.copy(url.openStream(), file);
+			System.out.println("Downloaded "  + url);
+			return true;
+		} catch (Exception e) {
+			System.err.println("debug: " + e);
+			return false;
+		}
+	}
+
+	/**
+	 * Converts a repo + path to a URL..
+	 * 
+	 * @param base
+	 *            The base repo
+	 * @param path
+	 *            The path in the directory + url
+	 * @return a URL that points to the file in the repo
+	 * 
+	 * @throws MalformedURLException
+	 */
+	URL toURL(URI base, String path) throws MalformedURLException {
+		StringBuilder r = new StringBuilder();
+		r.append(base.toString());
+		if (r.charAt(r.length() - 1) != '/')
+			r.append('/');
+		r.append(path);
+		return new URL(r.toString());
+	}
+
+	/**
+	 * Check if this is a valid cache directory, might probably need some more
+	 * stuff.
+	 * 
+	 * @return true if valid
+	 */
+	private boolean isValid() {
+		return pomFile.isFile() && pomFile.length() > 100 && artifactFile.isFile()
+				&& artifactFile.length() > 100;
+	}
+
+	/**
+	 * We maintain a set of bnd properties in the cache directory.
+	 * 
+	 * @param key
+	 *            The key for the property
+	 * @param value
+	 *            The value for the property
+	 */
+	private void setProperty(String key, String value) {
+		Properties properties = getProperties();
+		properties.setProperty(key, value);
+		propertiesChanged = true;
+	}
+
+	/**
+	 * Answer the properties, loading if needed.
+	 */
+	protected Properties getProperties() {
+		if (properties == null) {
+			properties = new Properties();
+			File props = new File(dir, "bnd.properties");
+			if (props.exists()) {
+				try {
+					FileInputStream in = new FileInputStream(props);
+					properties.load(in);
+				} catch (Exception e) {
+					// we ignore for now, will handle it on safe
+				}
+			}
+		}
+		return properties;
+	}
+
+	/**
+	 * Answer a property.
+	 * 
+	 * @param key
+	 *            The key
+	 * @return The value
+	 */
+	private String getProperty(String key) {
+		Properties properties = getProperties();
+		return properties.getProperty(key);
+	}
+
+	private void saveProperties() throws IOException {
+		if (propertiesChanged) {
+			FileOutputStream fout = new FileOutputStream(propertiesFile);
+			try {
+				properties.store(fout, "");
+			} finally {
+				properties = null;
+				propertiesChanged = false;
+				fout.close();
+			}
+		}
+	}
+
+	/**
+	 * Help function to create the POM and record its source.
+	 * 
+	 * @param url
+	 *            the repo from which it was constructed
+	 * @return the new pom
+	 * @throws Exception
+	 */
+	private CachedPom createPom(URI url) throws Exception {
+		CachedPom pom = new CachedPom(this, url);
+		pom.parse();
+		poms.put(url, pom);
+		setProperty(url.toASCIIString(), "true");
+		return pom;
+	}
+
+	/**
+	 * Verify that the repo has a checksum file for the given path and that this
+	 * checksum matchs.
+	 * 
+	 * @param repo
+	 *            The repo
+	 * @param path
+	 *            The file id
+	 * @return true if there is a digest and it matches one of the algorithms
+	 * @throws Exception
+	 */
+	boolean verify(URI repo, String path) throws Exception {
+		for (String algorithm : Maven.ALGORITHMS) {
+			if (verify(repo, path, algorithm))
+				return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Verify the path against its digest for the given algorithm.
+	 * 
+	 * @param repo
+	 * @param path
+	 * @param algorithm
+	 * @return
+	 * @throws Exception
+	 */
+	private boolean verify(URI repo, String path, String algorithm) throws Exception {
+		String digestPath = path + "." + algorithm;
+		File actualFile = new File(root, path);
+
+		if (download(repo, digestPath)) {
+			File digestFile = new File(root, digestPath);
+			final MessageDigest md = MessageDigest.getInstance(algorithm);
+			IO.copy(actualFile, new OutputStream() {
+				@Override public void write(int c) throws IOException {
+					md.update((byte) c);
+				}
+
+				@Override public void write(byte[] buffer, int offset, int length) {
+					md.update(buffer, offset, length);
+				}
+			});
+			byte[] digest = md.digest();
+			String source = IO.collect(digestFile).toUpperCase();
+			String hex = Hex.toHexString(digest).toUpperCase();
+			if (source.startsWith(hex)) {
+				System.out.println("Verified ok " + actualFile + " digest " + algorithm);
+				return true;
+			}
+		}
+		System.out.println("Failed to verify " + actualFile + " for digest " + algorithm);
+		return false;
+	}
+
+	public File getArtifact() throws Exception {
+		if (artifact == null )
+			return artifactFile;
+		return artifact.get();
+	}
+
+	public File getPomFile() {
+		return pomFile;
+	}
+
+	public void close() throws IOException {
+
+	}
+
+	public void remove() {
+		if (dir.getParentFile() != null) {
+			IO.delete(dir);
+		}
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
new file mode 100644
index 0000000..bbe2411
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
@@ -0,0 +1,130 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class MavenRemoteRepository implements RepositoryPlugin, RegistryPlugin, Plugin {
+	Reporter	reporter;
+	URI[]		repositories;
+	Registry	registry;
+	Maven		maven;
+
+	public File[] get(String bsn, String range) throws Exception {
+		File f = get(bsn, range, Strategy.HIGHEST, null);
+		if (f == null)
+			return null;
+
+		return new File[] { f };
+	}
+
+	public File get(String bsn, String version, Strategy strategy, Map<String, String> properties)
+			throws Exception {
+		String groupId = null;
+		
+		if (properties != null)
+			groupId = properties.get("groupId");
+		
+		if (groupId == null) {
+			int n = bsn.indexOf('+');
+			if ( n < 0)
+				return null;
+			
+			groupId = bsn.substring(0,n);
+			bsn = bsn.substring(n+1);
+		}
+
+		String artifactId = bsn;
+
+		if (version == null) {
+			if (reporter != null)
+				reporter.error("Maven dependency version not set for %s - %s", groupId, artifactId);
+			return null;
+		}
+
+		CachedPom pom = getMaven().getPom(groupId, artifactId, version, repositories);
+
+		String value = properties == null ? null : properties.get("scope");
+		if (value == null)
+			return pom.getArtifact();
+
+		Pom.Scope action = null;
+
+		try {
+			action = Pom.Scope.valueOf(value);
+			return pom.getLibrary(action, repositories);
+		} catch (Exception e) {
+			return pom.getArtifact();
+		}
+	}
+
+	public Maven getMaven() {
+		if ( maven != null)
+			return maven;
+		
+		maven = registry.getPlugin(Maven.class);
+		return maven;
+	}
+
+	public boolean canWrite() {
+		return false;
+	}
+
+	public File put(Jar jar) throws Exception {
+		throw new UnsupportedOperationException("cannot do put");
+	}
+
+	public List<String> list(String regex) throws Exception {
+		throw new UnsupportedOperationException("cannot do list");
+	}
+
+	public List<Version> versions(String bsn) throws Exception {
+		throw new UnsupportedOperationException("cannot do versions");
+	}
+
+	public String getName() {
+		return "maven";
+	}
+
+	public void setRepositories(URI... urls) {
+		repositories = urls;
+	}
+
+	public void setProperties(Map<String, String> map) {
+		String repoString = map.get("repositories");
+		if (repoString != null) {
+			String[] repos = repoString.split("\\s*,\\s*");
+			repositories = new URI[repos.length];
+			int n = 0;
+			for (String repo : repos) {
+				try {
+					URI uri = new URI(repo);
+					if ( !uri.isAbsolute())
+						uri = IO.getFile( new File(""),repo).toURI();
+					repositories[n++] = uri;
+				} catch (Exception e) {
+					if (reporter != null)
+						reporter.error("Invalid repository %s for maven plugin, %s", repo, e);
+				}
+			}
+		}
+	}
+
+	public void setReporter(Reporter reporter) {
+		this.reporter = reporter;
+	}
+
+	public void setRegistry(Registry registry) {
+		this.registry = registry;
+	}
+
+	public void setMaven(Maven maven) {
+		this.maven = maven;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/Pom.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/Pom.java
new file mode 100644
index 0000000..2b7ab46
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/Pom.java
@@ -0,0 +1,350 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import javax.xml.parsers.*;
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+public abstract class Pom {
+	static DocumentBuilderFactory	dbf	= DocumentBuilderFactory.newInstance();
+	static XPathFactory				xpf	= XPathFactory.newInstance();
+
+	static {
+		dbf.setNamespaceAware(false);
+	}
+	
+	public enum Scope {
+		compile, runtime, system, import_, provided, test, ;
+		
+		private boolean includes(Scope other) {
+			if (other == this) return true;
+			switch (this) {
+			case compile:
+				return other == provided || other == test;
+			default:
+				return false;
+			}
+		}
+	};
+
+	final Maven			maven;
+	final URI			home;
+
+	String				groupId;
+	String				artifactId;
+	String				version;
+	List<Dependency>	dependencies	= new ArrayList<Dependency>();
+	Exception			exception;
+	File				pomFile;
+	String				description="";
+	String				name;
+
+	public String getDescription() {
+		return description;
+	}
+
+	public class Dependency {
+		Scope		scope;
+		String		type;
+		boolean		optional;
+		String		groupId;
+		String		artifactId;
+		String		version;
+		Set<String>	exclusions	= new HashSet<String>();
+
+		public Scope getScope() {
+			return scope;
+		}
+
+		public String getType() {
+			return type;
+		}
+
+		public boolean isOptional() {
+			return optional;
+		}
+
+		public String getGroupId() {
+			return replace(groupId);
+		}
+
+		public String getArtifactId() {
+			return replace(artifactId);
+		}
+
+		public String getVersion() {
+			return replace(version);
+		}
+
+		public Set<String> getExclusions() {
+			return exclusions;
+		}
+
+		public Pom getPom() throws Exception {
+			return maven.getPom(groupId, artifactId, version);
+		}
+		@Override
+		public String toString() {
+			StringBuilder builder = new StringBuilder();
+			builder.append("Dependency [");
+			if (groupId != null)
+				builder.append("groupId=").append(groupId).append(", ");
+			if (artifactId != null)
+				builder.append("artifactId=").append(artifactId).append(", ");
+			if (version != null)
+				builder.append("version=").append(version).append(", ");
+			if (type != null)
+				builder.append("type=").append(type).append(", ");
+			if (scope != null)
+				builder.append("scope=").append(scope).append(", ");
+			builder.append("optional=").append(optional).append(", ");
+			if (exclusions != null)
+				builder.append("exclusions=").append(exclusions);
+			builder.append("]");
+			return builder.toString();
+		}
+	}
+
+	public Pom(Maven maven, File pomFile, URI home) throws Exception {
+		this.maven = maven;
+		this.home = home;
+		this.pomFile = pomFile;
+	}
+
+	void parse() throws Exception {
+		DocumentBuilder db = dbf.newDocumentBuilder();
+		System.out.println("Parsing " + pomFile.getAbsolutePath());
+		Document doc = db.parse(pomFile);
+		XPath xp = xpf.newXPath();
+		parse(doc, xp);
+	}
+
+	protected void parse(Document doc, XPath xp) throws XPathExpressionException, Exception {
+
+		this.artifactId = replace(xp.evaluate("project/artifactId", doc).trim(), this.artifactId);
+		this.groupId = replace(xp.evaluate("project/groupId", doc).trim(), this.groupId);
+		this.version = replace(xp.evaluate("project/version", doc).trim(), this.version);
+		
+		String nextDescription = xp.evaluate("project/description", doc).trim();
+		if ( this.description.length() != 0 && nextDescription.length() != 0)
+			this.description += "\n";
+		this.description += replace(nextDescription);
+		
+		this.name = replace(xp.evaluate("project/name", doc).trim(), this.name);
+
+		NodeList list = (NodeList) xp.evaluate("project/dependencies/dependency", doc,
+				XPathConstants.NODESET);
+		for (int i = 0; i < list.getLength(); i++) {
+			Node node = list.item(i);
+			Dependency dep = new Dependency();
+			String scope = xp.evaluate("scope", node).trim();
+			if (scope.length() == 0)
+				dep.scope = Scope.compile;
+			else
+				dep.scope = Scope.valueOf(scope);
+			dep.type = xp.evaluate("type", node).trim();
+
+			String opt = xp.evaluate("optional", node).trim();
+			dep.optional = opt != null && opt.equalsIgnoreCase("true");
+			dep.groupId = replace(xp.evaluate("groupId", node));
+			dep.artifactId = replace(xp.evaluate("artifactId", node).trim());
+
+			dep.version = replace(xp.evaluate("version", node).trim());
+			dependencies.add(dep);
+
+			NodeList exclusions = (NodeList) xp
+					.evaluate("exclusions", node, XPathConstants.NODESET);
+			for (int e = 0; e < exclusions.getLength(); e++) {
+				Node exc = exclusions.item(e);
+				String exclGroupId = xp.evaluate("groupId", exc).trim();
+				String exclArtifactId = xp.evaluate("artifactId", exc).trim();
+				dep.exclusions.add(exclGroupId + "+" + exclArtifactId);
+			}
+		}
+
+	}
+
+	private String replace(String key, String dflt) {
+		if ( key == null || key.length() == 0)
+			return dflt;
+		
+		return replace(key);
+	}
+
+	public String getArtifactId() throws Exception {
+		return replace(artifactId);
+	}
+
+	public String getGroupId() throws Exception {
+		return replace(groupId);
+	}
+
+	public String getVersion() throws Exception {
+		if ( version == null)
+			return "<not set>";
+		return replace(version);
+	}
+
+	public List<Dependency> getDependencies() throws Exception {
+		return dependencies;
+	}
+
+	class Rover {
+
+		public Rover(Rover rover, Dependency d) {
+			this.previous = rover;
+			this.dependency = d;
+		}
+
+		final Rover			previous;
+		final Dependency	dependency;
+
+		public boolean excludes(String name) {
+			return dependency.exclusions.contains(name) && previous != null
+					&& previous.excludes(name);
+		}
+	}
+
+	public Set<Pom> getDependencies(Scope scope, URI... urls) throws Exception {
+		Set<Pom> result = new LinkedHashSet<Pom>();
+
+		List<Rover> queue = new ArrayList<Rover>();
+		for (Dependency d : dependencies) {
+			queue.add(new Rover(null, d));
+		}
+
+		while (!queue.isEmpty()) {
+			Rover rover = queue.remove(0);
+			Dependency dep = rover.dependency;
+			String groupId = replace(dep.groupId);
+			String artifactId = replace(dep.artifactId);
+			String version = replace(dep.version);
+
+			String name = groupId + "+" + artifactId;
+
+			if (rover.excludes(name) || dep.optional)
+				continue;
+			
+			if (dep.scope == scope && !dep.optional) {
+				try {
+					Pom sub = maven.getPom(groupId, artifactId, version, urls);
+					if (sub != null) {
+						if (!result.contains(sub)) {
+							result.add(sub);
+							for (Dependency subd : sub.dependencies) {
+								queue.add(new Rover(rover, subd));
+							}
+						}
+					} else
+						if (rover.previous != null)
+							System.out.println("Cannot find " + dep + " from "
+									+ rover.previous.dependency);
+						else
+							System.out.println("Cannot find " + dep + " from top");
+				} catch (Exception e) {
+					if (rover.previous != null)
+						System.out.println("Cannot find " + dep + " from "
+								+ rover.previous.dependency);
+					else
+						System.out.println("Cannot find " + dep + " from top");
+
+//			boolean include = false;
+//			if (dep.scope == Scope.compile) {
+//				include = true;
+//			} else if (dep.scope == Scope.test) {
+//				include = rover.previous == null && (action == Action.compile || action == Action.test);
+//			} else if (dep.scope == Scope.runtime) {
+//				include = action == Action.run;
+//			}
+//			if (include) {
+//				Pom sub = maven.getPom(groupId, artifactId, version, urls);
+//				if (!result.contains(sub)) {
+//					result.add(sub);
+//					for (Dependency subd : sub.dependencies) {
+//						queue.add(new Rover(rover, subd));
+//					}
+					
+				}
+			}
+		}
+		return result;
+	}
+
+	protected String replace(String in) {
+		System.out.println("replace: " + in);
+		if (in == null)
+			return "null";
+
+		in = in.trim();
+		if ("${pom.version}".equals(in) || "${version}".equals(in)
+				|| "${project.version}".equals(in))
+			return version;
+
+		if ("${basedir}".equals(in))
+			return pomFile.getParentFile().getAbsolutePath();
+
+		if ("${pom.name}".equals(in) || "${project.name}".equals(in))
+			return name;
+
+		if ("${pom.artifactId}".equals(in) || "${project.artifactId}".equals(in))
+			return artifactId;
+		if ("${pom.groupId}".equals(in) || "${project.groupId}".equals(in))
+			return groupId;
+
+		return in;
+	}
+
+	public String toString() {
+		return groupId + "+" + artifactId + "-" + version;
+	}
+
+	public File getLibrary(Scope action, URI... repositories) throws Exception {
+		MavenEntry entry = maven.getEntry(this);
+		File file = new File(entry.dir, action + ".lib");
+
+		if (file.isFile() && file.lastModified() >= getPomFile().lastModified())
+			return file;
+
+		file.delete();
+
+		Writer writer = new FileWriter(file);
+		doEntry(writer, this);
+		try {
+			for (Pom dep : getDependencies(action, repositories)) {
+				doEntry(writer, dep);
+			}
+		} finally {
+			writer.close();
+		}
+		return file;
+	}
+
+	/**
+	 * @param writer
+	 * @param dep
+	 * @throws IOException
+	 * @throws Exception
+	 */
+	private void doEntry(Writer writer, Pom dep) throws IOException, Exception {
+		writer.append(dep.getGroupId());
+		writer.append("+");
+		writer.append(dep.getArtifactId());
+		writer.append(";version=\"");
+		writer.append(dep.getVersion());
+		writer.append("\"\n");
+	}
+
+	public File getPomFile() {
+		return pomFile;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public abstract java.io.File getArtifact() throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/ProjectPom.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/ProjectPom.java
new file mode 100644
index 0000000..8187954
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/ProjectPom.java
@@ -0,0 +1,204 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+import aQute.lib.io.*;
+
+public class ProjectPom extends Pom {
+
+	final List<URI>	repositories	= new ArrayList<URI>();
+	final Properties	properties		= new Properties();
+	String 		packaging;
+	String 		url;
+	
+	ProjectPom(Maven maven, File pomFile) throws Exception {
+		super(maven, pomFile, pomFile.toURI());
+	}
+
+	@Override protected void parse(Document doc, XPath xp) throws Exception {
+
+		packaging = xp.evaluate("project/packaging", doc);
+		url = xp.evaluate("project/url", doc);
+		
+		Node parent = (Node) xp.evaluate("project/parent", doc, XPathConstants.NODE);
+		if (parent != null && parent.hasChildNodes()) {
+			File parentFile = IO.getFile(getPomFile().getParentFile(), "../pom.xml");
+
+			String parentGroupId = xp.evaluate("groupId", parent).trim();
+			String parentArtifactId = xp.evaluate("artifactId", parent).trim();
+			String parentVersion = xp.evaluate("version", parent).trim();
+			String parentPath = xp.evaluate("relativePath", parent).trim();
+			if (parentPath != null && parentPath.length()!=0) {
+				parentFile = IO.getFile(getPomFile().getParentFile(), parentPath);
+			}
+			if (parentFile.isFile()) {
+				ProjectPom parentPom = new ProjectPom(maven, parentFile);
+				parentPom.parse();
+				dependencies.addAll(parentPom.dependencies);
+				for ( Enumeration<?> e = parentPom.properties.propertyNames(); e.hasMoreElements(); ) {
+					String key = (String) e.nextElement();
+					if ( ! properties.contains(key))
+						properties.put(key, parentPom.properties.get(key));
+				}
+				repositories.addAll(parentPom.repositories);
+				
+				setNames(parentPom);
+			} else {
+				// This seems to be a bit bizarre, extending an external pom?
+				CachedPom parentPom = maven.getPom(parentGroupId, parentArtifactId, parentVersion);
+				dependencies.addAll(parentPom.dependencies);
+				setNames(parentPom);
+			}
+		}
+
+		NodeList propNodes = (NodeList) xp.evaluate("project/properties/*", doc,
+				XPathConstants.NODESET);
+		for (int i = 0; i < propNodes.getLength(); i++) {
+			Node node = propNodes.item(i);
+			String key = node.getNodeName();
+			String value = node.getTextContent();
+			if ( key == null || key.length()==0)
+				throw new IllegalArgumentException("Pom has an empty or null key");
+			if ( value == null || value.length()==0)
+				throw new IllegalArgumentException("Pom has an empty or null value for property " + key);
+			properties.setProperty(key, value.trim());
+		}
+
+		NodeList repos = (NodeList) xp.evaluate("project/repositories/repository/url", doc,
+				XPathConstants.NODESET);
+		for (int i = 0; i < repos.getLength(); i++) {
+			Node node = repos.item(i);
+			String URIString = node.getTextContent().trim();
+			URI uri = new URI(URIString);
+			if ( uri.getScheme() ==null )
+				uri = IO.getFile(pomFile.getParentFile(),URIString).toURI();
+			repositories.add(uri);
+		}
+
+		super.parse(doc, xp);
+	}
+
+//	private void print(Node node, String indent) {
+//		System.out.print(indent);
+//		System.out.println(node.getNodeName());
+//		Node rover = node.getFirstChild();
+//		while ( rover != null) {
+//			print( rover, indent+" ");
+//			rover = rover.getNextSibling();
+//		}
+//	}
+
+	/**
+	 * @param parentArtifactId
+	 * @param parentGroupId
+	 * @param parentVersion
+	 * @throws Exception
+	 */
+	private void setNames(Pom pom) throws Exception {
+		if (artifactId == null || artifactId.length()==0)
+			artifactId = pom.getArtifactId();
+		if (groupId == null || groupId.length()==0)
+			groupId = pom.getGroupId();
+		if (version == null || version.length()==0)
+			version = pom.getVersion();
+		if ( description == null )
+			description = pom.getDescription();
+		else
+			description = pom.getDescription() + "\n" + description;
+	
+	}
+
+	class Rover {
+
+		public Rover(Rover rover, Dependency d) {
+			this.previous = rover;
+			this.dependency = d;
+		}
+
+		final Rover			previous;
+		final Dependency	dependency;
+
+		public boolean excludes(String name) {
+			return dependency.exclusions.contains(name) && previous != null
+					&& previous.excludes(name);
+		}
+	}
+
+	public Set<Pom> getDependencies(Scope action) throws Exception {
+		return getDependencies(action, repositories.toArray(new URI[0]));
+	}
+
+	// Match any macros
+	final static Pattern	MACRO	= Pattern.compile("(\\$\\{\\s*([^}\\s]+)\\s*\\})");
+
+	protected String replace(String in) {
+		System.out.println("Replce: " + in);
+		if ( in == null) {
+			System.out.println("null??");
+		}
+		Matcher matcher = MACRO.matcher(in);
+		int last = 0;
+		StringBuilder sb = new StringBuilder();
+		while (matcher.find()) {
+			int n = matcher.start();
+			sb.append( in, last, n);
+			String replacement = get(matcher.group(2));
+			if ( replacement == null )
+				sb.append( matcher.group(1));
+			else
+				sb.append( replacement );
+			last = matcher.end();
+		}
+		if ( last == 0)
+			return in;
+		
+		sb.append( in, last, in.length());
+		return sb.toString();
+	}
+
+	private String get(String key) {
+		if (key.equals("pom.artifactId"))
+			return artifactId;
+		if (key.equals("pom.groupId"))
+			return groupId;
+		if (key.equals("pom.version"))
+			return version;
+		
+		if (key.equals("pom.name"))
+			return name;
+		
+		String prop = properties.getProperty(key);
+		if ( prop != null )
+			return prop;
+		
+		return System.getProperty(key);
+	}
+
+	public Properties getProperties() {
+		return properties;
+	}
+
+	public String getPackaging() {
+		return packaging;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public String getProperty(String key) {
+		String s = properties.getProperty(key);
+		return replace(s);
+	}
+
+	@Override public File getArtifact() throws Exception {
+		return null;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/Repo.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/Repo.java
new file mode 100644
index 0000000..dd1939f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/Repo.java
@@ -0,0 +1,5 @@
+package aQute.bnd.maven.support;
+
+public class Repo {
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/repo/eclipse/EclipseRepo.java b/bundleplugin/src/main/java/aQute/bnd/repo/eclipse/EclipseRepo.java
new file mode 100644
index 0000000..3fa967a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/repo/eclipse/EclipseRepo.java
@@ -0,0 +1,199 @@
+package aQute.bnd.repo.eclipse;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.generics.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class EclipseRepo implements Plugin, RepositoryPlugin {
+    File                             root;
+    Reporter                         reporter;
+    String                           name;
+    Map<String, Map<String, String>> index;
+
+    public static String             LOCATION = "location";
+    public static String             NAME     = "name";
+
+    public void setProperties(Map<String, String> map) {
+        String location = (String) map.get(LOCATION);
+        if (location == null)
+            throw new IllegalArgumentException(
+                    "Location muse be set on a EclipseRepo plugin");
+
+        root = new File(location);
+        if (!root.isDirectory())
+            throw new IllegalArgumentException(
+                    "Repository is not a valid directory " + root);
+
+        if (!new File(root, "plugins").isDirectory())
+            throw new IllegalArgumentException(
+                    "Repository is not a valid directory (no plugins directory)"
+                            + root);
+
+        name = (String) map.get(NAME);
+
+        try {
+            index = buildIndex();
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Could not build index for eclipse repo: " + root);
+        }
+    }
+
+    Map<String, Map<String, String>> buildIndex() throws Exception {
+        File index = new File(root, "bnd.index").getAbsoluteFile();
+        File[] plugins = new File(root, "plugins").listFiles();
+
+        for (File f : plugins) {
+            f = f.getAbsoluteFile();
+            if (f.isFile()) {
+                if (f.lastModified() > index.lastModified()) {
+
+                    Map<String, Map<String, String>> map = buildIndex(plugins);
+                    write(index, map);
+                    return map;
+                }
+            }
+        }
+
+        String s = read(index);
+        return Processor.parseHeader(s, null);
+    }
+
+    private String read(File index) throws Exception {
+        if (index.isFile()) {
+            BufferedReader fr = new BufferedReader(new FileReader(index));
+            StringBuilder sb = new StringBuilder();
+
+            try {
+                String s = fr.readLine();
+                while (s != null) {
+                    sb.append(s);
+                    s = fr.readLine();
+                }
+            } finally {
+                fr.close();
+            }
+        }
+        return null;
+    }
+
+    private void write(File index, Map<String, Map<String, String>> map)
+            throws Exception {
+        String s = Processor.printClauses(map);
+        index.getParentFile().mkdirs();
+        FileWriter fw = new FileWriter(index);
+        try {
+            fw.write(s);
+        } finally {
+            fw.close();
+        }
+    }
+
+    private Map<String, Map<String, String>> buildIndex(File[] plugins) {
+        Map<String, Map<String, String>> map = Create.map();
+        for (File plugin : plugins) {
+            try {
+                Jar jar = new Jar(plugin);
+                Manifest manifest = jar.getManifest();
+                String bsn = manifest.getMainAttributes().getValue(
+                        Constants.BUNDLE_SYMBOLICNAME);
+                String version = manifest.getMainAttributes().getValue(
+                        Constants.BUNDLE_VERSION);
+
+                if (bsn != null) {
+                    if (version == null)
+                        version = "0";
+
+                    Map<String, String> instance = map.get(bsn);
+                    if (instance == null) {
+                        instance = Create.map();
+                    }
+                    instance.put(version, plugin.getAbsolutePath());
+                }
+            } catch (Exception e) {
+                // Ignore exceptions in the plugins dir.
+            }
+        }
+        return map;
+    }
+
+    public void setReporter(Reporter reporter) {
+        this.reporter = reporter;
+    }
+
+    public boolean canWrite() {
+        return false;
+    }
+
+    public File[] get(String bsn, String range) throws Exception {
+        VersionRange r = new VersionRange(range);
+        Map<String, String> instances = index.get(bsn);
+        if (instances == null)
+            return null;
+
+        List<File> result = Create.list();
+
+        for (String version : instances.keySet()) {
+            Version v = new Version(version);
+            if (r.includes(v)) {
+                File f = new File(instances.get(version));
+                if (f.isFile()) {
+                    result.add(f);
+                }
+            }
+        }
+        return result.toArray(new File[result.size()]);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<String> list(String regex) {
+        Instruction pattern = null;
+        if (regex != null)
+            pattern = Instruction.getPattern(regex);
+
+        List<String> result = new ArrayList<String>();
+        for (String f : index.keySet()) {
+            if (pattern == null || pattern.matches(f))
+                result.add(f);
+        }
+        return result;
+    }
+
+    public File put(Jar jar) throws Exception {
+        return null;
+    }
+
+    public List<Version> versions(String bsn) {
+        Map<String, String> instances = index.get(bsn);
+        if (instances == null)
+            return null;
+
+        List<Version> versions = Create.list();
+        for (String v : instances.keySet())
+            versions.add(new Version(v));
+        return versions;
+    }
+
+
+	public File get(String bsn, String range, Strategy strategy, Map<String,String> properties) throws Exception {
+		File[] files = get(bsn, range);
+		if (files.length >= 0) {
+			switch (strategy) {
+			case LOWEST:
+				return files[0];
+			case HIGHEST:
+				return files[files.length - 1];
+			}
+		}
+		return null;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/resolver/Resolution.java b/bundleplugin/src/main/java/aQute/bnd/resolver/Resolution.java
new file mode 100644
index 0000000..b703835
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/resolver/Resolution.java
@@ -0,0 +1,12 @@
+package aQute.bnd.resolver;
+
+import java.util.*;
+
+import aQute.bnd.resolver.Resource.Requirement;
+import aQute.lib.collections.*;
+
+public class Resolution {
+	public Set<Requirement>				unresolved;
+	public MultiMap<Requirement, Resource>	multiple;
+	public Map<Requirement, Resource>		unique;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/resolver/Resolver.java b/bundleplugin/src/main/java/aQute/bnd/resolver/Resolver.java
new file mode 100644
index 0000000..4457f16
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/resolver/Resolver.java
@@ -0,0 +1,172 @@
+package aQute.bnd.resolver;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.build.*;
+import aQute.bnd.resolver.Resource.Requirement;
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.libg.generics.*;
+
+public class Resolver extends Processor {
+
+	final Set<Resource>									resources	= new HashSet<Resource>();
+	private Map<Resource.Requirement, Set<Resource>>	cache		= new IdentityHashMap<Resource.Requirement, Set<Resource>>();
+
+	public void add(Container c) throws Exception {
+		List<File> files = new ArrayList<File>();
+		c.contributeFiles(files, this);
+		for (File f : files) {
+			add(f);
+		}
+	}
+
+	public void addAll(Collection<Container> containers) throws Exception {
+		for (Container c : containers) {
+			add(c);
+		}
+	}
+
+	public Resolution resolve() throws Exception {
+		// Split fragments and bundles
+		Set<Resource> active = new HashSet<Resource>();
+		Set<Resource> fragments = new HashSet<Resource>();
+		Set<Resource> singletons = new HashSet<Resource>();
+
+		for (Resource r : resources) {
+			if (r.fragments != null) {
+				active.add(r);
+				if (r.singleton)
+					singletons.add(r);
+			} else
+				fragments.add(r);
+		}
+
+		// Attach fragments
+		for (Resource r : fragments) {
+			Collection<Resource> hosts = find(active, r.requirements, new HashSet<Resource>());
+			for (Resource host : hosts) {
+				host.fragments.add(host);
+			}
+		}
+
+		// Create a list of all the requirements
+		Set<Resource.Requirement> reqs = new HashSet<Resource.Requirement>();
+		for (Resource r : active) {
+			reqs.addAll(r.requirements);
+			// And its attached fragments
+			for (Resource f : r.fragments) {
+				reqs.addAll(f.requirements);
+			}
+		}
+
+		Set<Resource.Requirement> optional = Create.set();
+		Set<Resource.Requirement> unresolved = Create.set();
+		Map<Resource.Requirement, Resource> unique = Create.map();
+		MultiMap<Requirement, Resource> multiple = new MultiMap<Requirement, Resource>();
+
+		for (Resource.Requirement req : reqs) {
+			Collection<Resource> solutions = find(active, req, new HashSet<Resource>());
+			if (solutions.isEmpty()) {
+				if (req.optional)
+					optional.add(req);
+				else
+					unresolved.add(req);
+			} else if (solutions.size() == 1)
+				unique.put(req, solutions.iterator().next());
+			else {
+				multiple.addAll(req, solutions);
+			}
+		}
+
+		// If we have unresolveds, tough shit
+
+		if (!unresolved.isEmpty()) {
+			for (Requirement r : unresolved) {
+				error("Unresolved %s", r);
+			}
+		}
+
+		// Calculate our singleton candidates
+		MultiMap<String, Resource> picked = new MultiMap<String, Resource>();
+		for (Resource r : singletons) {
+			picked.add(r.bsn, r);
+		}
+
+		// Remove any singletons that are alone
+		// and verify that if there are multiple they are not
+		// both in the unique solutions
+		for (Iterator<Map.Entry<String, Set<Resource>>> i = picked.entrySet().iterator(); i
+				.hasNext();) {
+			Map.Entry<String, Set<Resource>> entry = i.next();
+			if (entry.getValue().size() == 1)
+				i.remove();
+			else {
+				Set<Resource> x = new HashSet<Resource>(entry.getValue());
+				boolean changed = x.retainAll(unique.values());
+				if (x.size() > 1) {
+					// We need multiple singleton bundles with the same bsn
+					error("Singleton conflict: %s", x);
+				} else if (changed) {
+					Set<Resource> delta = new HashSet<Resource>(entry.getValue());
+					delta.removeAll(x);
+
+					// We've removed bundles from the possible solutions
+					for (Iterator<Resource> it = multiple.all(); i.hasNext();) {
+						Resource r = it.next();
+						if (delta.contains(r)) {
+							it.remove();
+						}
+					}
+				}
+			}
+		}
+
+		Resolution res = new Resolution();
+		res.multiple = multiple;
+		res.unique = unique;
+		res.unresolved = unresolved;
+		return res;
+	}
+
+	private Collection<Resource> find(Set<Resource> active, Set<Resource.Requirement> requirements,
+			Set<Resource> result) {
+		for (Resource.Requirement req : requirements) {
+			Set<Resource> resources = cache.get(req);
+			if (resources != null) {
+				result.addAll(resources);
+			} else {
+				resources = find(active, req, new HashSet<Resource>());
+				cache.put(req, resources);
+				result.addAll(resources);
+			}
+		}
+		return result;
+	}
+
+	private Set<Resource> find(Set<Resource> active, Requirement req, Set<Resource> result) {
+		for (Resource r : active) {
+			for (Resource.Capability cap : r.capabilities) {
+				if ( cap.name.equals(req.name))
+					System.out.println("Yes");
+				if (req.matches(cap))
+					result.add(r);
+			}
+		}
+		return result;
+	}
+
+	public void add(File file) throws IOException {
+		JarFile jf = new JarFile(file);
+		try {
+			Manifest m = jf.getManifest();
+			Resource r = new Resource(this, m);
+			resources.add(r);
+		} finally {
+			jf.close();
+		}
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/resolver/Resource.java b/bundleplugin/src/main/java/aQute/bnd/resolver/Resource.java
new file mode 100644
index 0000000..54306d3
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/resolver/Resource.java
@@ -0,0 +1,161 @@
+package aQute.bnd.resolver;
+
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.lib.osgi.*;
+import aQute.libg.version.*;
+
+public class Resource {
+	enum Type {
+		PACKAGE,
+		BUNDLE,
+		HOST;
+	}
+	
+	final Set<Requirement>	requirements	= new HashSet<Requirement>();
+	final Set<Capability>	capabilities	= new HashSet<Capability>();
+	final List<Resource>	fragments;
+	final String			bsn;
+	final Version			version;
+	final boolean			singleton;
+	final Resolver			resolver;
+
+	class Requirement {
+		final Type		type;
+		final String		name;
+		final VersionRange	range;
+		final boolean		optional;
+
+		Requirement(Type type, String name, VersionRange range, boolean optional) {
+			this.type = type;
+			this.name = name;
+			this.range = range;
+			this.optional = optional;
+		}
+
+		Resource getDeclaredResource() {
+			return Resource.this;
+		}
+
+		public boolean matches(Capability cap) {
+			boolean a = cap.type == type;
+			boolean b = cap.name.equals(name);
+			boolean c = range.includes(cap.version);
+			return a && b && c;
+		}
+		
+		public String toString() {
+			return "R." +type + ":" + name + "-" + range;
+		}
+
+	}
+
+	class Capability {
+		final Type	type;
+		final String	name;
+		final Version	version;
+
+		Capability(Type type, String name, Version version) {
+			this.type = type;
+			this.name = name;
+			this.version = version;
+		}
+
+		Resource getDeclaredResource() {
+			return Resource.this;
+		}
+		public String toString() {
+			return "C." + type + ":" + name + "-" + version;
+		}
+	}
+
+	Resource(Resolver resolver, Manifest m) {
+		this.resolver= resolver;
+		Attributes main = m.getMainAttributes();
+		Map<String, Map<String, String>> bsns = resolver.parseHeader(main
+				.getValue(Constants.BUNDLE_SYMBOLICNAME));
+		if (bsns.size() > 1)
+			resolver.error("Multiple bsns %s", bsns);
+		if (bsns.size() < 1) {
+			resolver.error("No bsns");
+			bsn = "<not set>";
+			version = new Version("0");
+			singleton = false;
+		} else {
+			Map.Entry<String, Map<String, String>> entry = bsns.entrySet().iterator().next();
+			bsn = entry.getKey();
+			String v = main.getValue(Constants.BUNDLE_VERSION);
+			this.version = version(v);
+			singleton = "true"
+					.equalsIgnoreCase(entry.getValue().get(Constants.SINGLETON_DIRECTIVE));
+
+			String directive = entry.getValue().get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
+			boolean attach = directive == null || "always".equals(directive) || "resolve-time".equals(directive);
+			if (attach) {
+				capabilities.add(new Capability(Type.HOST, bsn, version));
+			}
+		}
+		Map<String, Map<String, String>> hosts = resolver.parseHeader(main.getValue(Constants.FRAGMENT_HOST));
+		if (hosts.size() > 1)
+			resolver.error("Multiple fragment hosts %s", hosts);
+		if (hosts.size() == 1) {
+			Map.Entry<String, Map<String, String>> entry = hosts.entrySet().iterator().next();
+			String host = entry.getKey();
+			VersionRange range = range(entry.getValue().get(Constants.VERSION_ATTRIBUTE));
+			requirements.add(new Requirement(Type.HOST, host, range, false));
+			fragments = null;
+		} else {
+			fragments = new ArrayList<Resource>();
+			capabilities.add(new Capability(Type.BUNDLE, bsn, version));
+		}
+
+		Map<String, Map<String, String>> rbs = resolver.parseHeader(main.getValue(Constants.REQUIRE_BUNDLE));
+		for (Map.Entry<String, Map<String, String>> clause : rbs.entrySet()) {
+			boolean optional = "optional".equals(clause.getValue().get(
+					Constants.RESOLUTION_DIRECTIVE));
+			requirements.add(new Requirement(Type.BUNDLE, clause.getKey(), range(clause
+					.getValue().get(Constants.VERSION_ATTRIBUTE)), optional));
+		}
+		Map<String, Map<String, String>> imports = resolver.parseHeader(main
+				.getValue(Constants.IMPORT_PACKAGE));
+		for (Map.Entry<String, Map<String, String>> clause : imports.entrySet()) {
+			boolean optional = "optional".equals(clause.getValue().get(
+					Constants.RESOLUTION_DIRECTIVE));
+			requirements.add(new Requirement(Type.PACKAGE, clause.getKey(), range(clause
+					.getValue().get(Constants.VERSION_ATTRIBUTE)), optional));
+		}
+		Map<String, Map<String, String>> exports = resolver.parseHeader(main
+				.getValue(Constants.EXPORT_PACKAGE));
+		for (Map.Entry<String, Map<String, String>> clause : exports.entrySet()) {
+			capabilities.add(new Capability(Type.PACKAGE, clause.getKey(), version(clause
+					.getValue().get(Constants.VERSION_ATTRIBUTE))));
+		}
+	}
+
+	private VersionRange range(String string) {
+		try {
+			return new VersionRange(string);
+		} catch (NullPointerException e) {
+			return new VersionRange("0");
+		} catch (Exception e) {
+			resolver.error("Invalid version range: %s in %s-%s", string, bsn, version);
+			return new VersionRange("0");
+		}
+	}
+
+	private Version version(String string) {
+		try {
+			return new Version(string);
+		} catch (NullPointerException e) {
+			return new Version("0");
+		} catch (Exception e) {
+			resolver.error("Invalid version: %s in %s-%s", string, bsn, version);
+			return new Version("0");
+		}
+	}
+	
+	public String toString() {
+		return bsn + "-" + version;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/AnalyzerPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/AnalyzerPlugin.java
new file mode 100644
index 0000000..3efe8ee
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/AnalyzerPlugin.java
@@ -0,0 +1,20 @@
+package aQute.bnd.service;
+
+import aQute.lib.osgi.*;
+
+public interface AnalyzerPlugin {
+
+    /**
+     * This plugin is called after analysis. The plugin is free to modify the
+     * jar and/or change the classpath information (see referred, contained).
+     * This plugin is called after analysis of the JAR but before manifest
+     * generation.
+     * 
+     * @param analyzer
+     * @return true if the classpace has been modified so that the bundle
+     *         classpath must be reanalyzed
+     * @throws Exception
+     */
+
+    boolean analyzeJar(Analyzer analyzer) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/BndListener.java b/bundleplugin/src/main/java/aQute/bnd/service/BndListener.java
new file mode 100644
index 0000000..3ec6ef1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/BndListener.java
@@ -0,0 +1,17 @@
+package aQute.bnd.service;
+
+import java.io.*;
+import java.util.concurrent.atomic.*;
+
+public class BndListener {
+	final AtomicInteger inside = new AtomicInteger();
+	
+    public void changed(File file) {
+    }
+    public void begin() { inside.incrementAndGet();}
+    public void end() { inside.decrementAndGet(); }
+    
+    public boolean isInside() {
+    	return inside.get()!=0;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/CommandPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/CommandPlugin.java
new file mode 100644
index 0000000..34c72c2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/CommandPlugin.java
@@ -0,0 +1,30 @@
+package aQute.bnd.service;
+
+import aQute.bnd.build.*;
+
+/**
+ * A plugin that makes it possible to 
+ * @author aqute
+ *
+ */
+public interface CommandPlugin {
+    /**
+     * Is run before a command is executed. These plugins are called
+     * in the order of declaration.
+     * 
+     * @param project The project for which the command runs
+     * 
+     * @param command the command name
+     */
+    void before(Project project, String command);
+    
+    /**
+     * Is run after a command is executed. These plugins are
+     * called in the reverse order of declaration.
+     * 
+     * @param project The project for which the command runs
+     *
+     * @param command the command name
+     */
+    void after(Project project, String command, Throwable outcome);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Compiler.java b/bundleplugin/src/main/java/aQute/bnd/service/Compiler.java
new file mode 100644
index 0000000..626b68c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Compiler.java
@@ -0,0 +1,11 @@
+package aQute.bnd.service;
+
+import java.io.*;
+import java.util.*;
+
+import aQute.bnd.build.*;
+
+public interface Compiler {
+	boolean compile(Project project, Collection<File> sources, Collection<Container> buildpath,
+			File bin) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/DependencyContributor.java b/bundleplugin/src/main/java/aQute/bnd/service/DependencyContributor.java
new file mode 100644
index 0000000..e6a88ff
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/DependencyContributor.java
@@ -0,0 +1,9 @@
+package aQute.bnd.service;
+
+import java.util.*;
+
+import aQute.bnd.build.*;
+
+public interface DependencyContributor {
+    void addDependencies(Project project, Set<String> dependencies);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Deploy.java b/bundleplugin/src/main/java/aQute/bnd/service/Deploy.java
new file mode 100644
index 0000000..e1d92e1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Deploy.java
@@ -0,0 +1,12 @@
+package aQute.bnd.service;
+
+import aQute.bnd.build.*;
+import aQute.lib.osgi.*;
+
+/**
+ * Deploy this artifact to maven.
+ * 
+ */
+public interface Deploy {	
+	boolean deploy(Project project, Jar jar) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/EclipseJUnitTester.java b/bundleplugin/src/main/java/aQute/bnd/service/EclipseJUnitTester.java
new file mode 100644
index 0000000..a2af9b0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/EclipseJUnitTester.java
@@ -0,0 +1,6 @@
+package aQute.bnd.service;
+
+public interface EclipseJUnitTester {
+	void setPort(int port);
+	void setHost( String host);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/LauncherPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/LauncherPlugin.java
new file mode 100644
index 0000000..f858500
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/LauncherPlugin.java
@@ -0,0 +1,9 @@
+package aQute.bnd.service;
+
+import aQute.bnd.build.*;
+
+public interface LauncherPlugin {
+	ProjectLauncher getLauncher(Project project) throws Exception;
+
+	ProjectTester getTester(Project project);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/MakePlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/MakePlugin.java
new file mode 100644
index 0000000..20a1849
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/MakePlugin.java
@@ -0,0 +1,21 @@
+package aQute.bnd.service;
+
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+public interface MakePlugin {
+
+    /**
+     * This plugin is called when Include-Resource detects a reference to a resource
+     * that it can not find in the file system.
+     * 
+     * @param builder   The current builder
+     * @param source    The source string (i.e. the place where bnd looked)
+     * @param arguments Any arguments on the clause in Include-Resource
+     * @return          A resource or null if no resource could be made
+     * @throws Exception
+     */
+    Resource make(Builder builder, String source, Map<String,String> arguments) throws Exception;
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/OBRIndexProvider.java b/bundleplugin/src/main/java/aQute/bnd/service/OBRIndexProvider.java
new file mode 100644
index 0000000..fe12712
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/OBRIndexProvider.java
@@ -0,0 +1,11 @@
+package aQute.bnd.service;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Set;
+
+public interface OBRIndexProvider {
+	Collection<URL> getOBRIndexes() throws IOException;
+	Set<OBRResolutionMode> getSupportedModes();
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/OBRResolutionMode.java b/bundleplugin/src/main/java/aQute/bnd/service/OBRResolutionMode.java
new file mode 100644
index 0000000..f269d45
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/OBRResolutionMode.java
@@ -0,0 +1,5 @@
+package aQute.bnd.service;
+
+public enum OBRResolutionMode {
+	build, runtime
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Plugin.java b/bundleplugin/src/main/java/aQute/bnd/service/Plugin.java
new file mode 100644
index 0000000..065fac8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Plugin.java
@@ -0,0 +1,31 @@
+package aQute.bnd.service;
+
+import java.util.*;
+
+import aQute.libg.reporter.*;
+
+/**
+ * An optional interface for plugins. If a plugin implements this interface then
+ * it can receive the reminaing attributes and directives given in its clause as
+ * well as the reporter to use.
+ * 
+ */
+public interface Plugin {
+    /**
+     * Give the plugin the remaining properties.
+     * 
+     * When a plugin is declared, the clause can contain extra properties.
+     * All the properties and directives are given to the plugin to use.
+     * 
+     * @param map attributes and directives for this plugin's clause
+     */
+    void setProperties(Map<String,String> map);
+    
+    /**
+     * Set the current reporter. This is called at init time. This plugin
+     * should report all errors and warnings to this reporter.
+     * 
+     * @param processor
+     */
+    void setReporter(Reporter processor);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Refreshable.java b/bundleplugin/src/main/java/aQute/bnd/service/Refreshable.java
new file mode 100644
index 0000000..e5e62e9
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Refreshable.java
@@ -0,0 +1,8 @@
+package aQute.bnd.service;
+
+import java.io.*;
+
+public interface Refreshable {
+    boolean refresh();
+    File getRoot();
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Registry.java b/bundleplugin/src/main/java/aQute/bnd/service/Registry.java
new file mode 100644
index 0000000..90fca36
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Registry.java
@@ -0,0 +1,11 @@
+package aQute.bnd.service;
+
+import java.util.*;
+
+/**
+ * A registry for objects.
+ */
+public interface Registry {
+	<T> List<T> getPlugins(Class<T> c);
+	<T> T getPlugin(Class<T> c);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/RegistryPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/RegistryPlugin.java
new file mode 100644
index 0000000..7a46849
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RegistryPlugin.java
@@ -0,0 +1,9 @@
+package aQute.bnd.service;
+
+
+/**
+ * A plugin that wants a registry
+ */
+public interface RegistryPlugin {
+	void setRegistry(Registry registry);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java
new file mode 100644
index 0000000..cbd42b8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java
@@ -0,0 +1,17 @@
+package aQute.bnd.service;
+
+import java.util.Map;
+
+public interface RemoteRepositoryPlugin extends RepositoryPlugin {
+	/**
+	 * Retrieve a resource handle from the repository. For all implementations of this interface, calling {@code getFile(bsn, range, strategy, props)}
+	 * should always return the same result as {@code getResource(bsn, range, strategy, props).request()}.
+	 * @param bsn
+	 * @param range
+	 * @param strategy
+	 * @param properties
+	 * @return
+	 * @throws Exception
+	 */
+	ResourceHandle getHandle(String bsn, String range, Strategy strategy, Map<String,String> properties) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/RepositoryListenerPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryListenerPlugin.java
new file mode 100644
index 0000000..0044da3
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryListenerPlugin.java
@@ -0,0 +1,16 @@
+package aQute.bnd.service;
+
+import java.io.File;
+
+import aQute.lib.osgi.Jar;
+
+public interface RepositoryListenerPlugin {
+	
+	/**
+	 * Called when a bundle is added to a repository.
+	 * @param repository
+	 * @param jar
+	 * @param file
+	 */
+	void bundleAdded(RepositoryPlugin repository, Jar jar, File file);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java
new file mode 100644
index 0000000..a01c4b1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java
@@ -0,0 +1,86 @@
+package aQute.bnd.service;
+
+import java.io.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.libg.version.*;
+
+public interface RepositoryPlugin {
+	public enum Strategy {
+		LOWEST, HIGHEST, EXACT
+	}
+
+	/**
+	 * Return a URL to a matching version of the given bundle.
+	 * 
+	 * @param bsn
+	 *            Bundle-SymbolicName of the searched bundle
+	 * @param range
+	 *            Version range for this bundle,"latest" if you only want the
+	 *            latest, or null when you want all.
+	 * @return A list of URLs sorted on version, lowest version is at index 0.
+	 *         null is returned when no files with the given bsn ould be found.
+	 * @throws Exception
+	 *             when anything goes wrong
+	 */
+	@Deprecated File[] get(String bsn, String range) throws Exception;
+
+	/**
+	 * Return a URL to a matching version of the given bundle.
+	 * 
+	 * @param bsn
+	 *            Bundle-SymbolicName of the searched bundle
+	 * @param range
+	 *            Version range for this bundle,"latest" if you only want the
+	 *            latest, or null when you want all.
+	 * @param strategy
+	 *            Get the highest or the lowest
+	 * @return A list of URLs sorted on version, lowest version is at index 0.
+	 *         null is returned when no files with the given bsn ould be found.
+	 * @throws Exception
+	 *             when anything goes wrong
+	 */
+	File get(String bsn, String range, Strategy strategy, Map<String,String> properties) throws Exception;
+
+	/**
+	 * Answer if this repository can be used to store files.
+	 * 
+	 * @return true if writable
+	 */
+	boolean canWrite();
+
+	/**
+	 * Put a JAR file in the repository.
+	 * 
+	 * @param jar
+	 * @throws Exception
+	 */
+	File put(Jar jar) throws Exception;
+
+	/**
+	 * Return a list of bsns that are present in the repository.
+	 * 
+	 * @param regex
+	 *            if not null, match against the bsn and if matches, return
+	 *            otherwise skip
+	 * @return A list of bsns that match the regex parameter or all if regex is
+	 *         null
+	 * @throws Exception
+	 */
+	List<String> list(String regex) throws Exception;
+
+	/**
+	 * Return a list of versions.
+	 * 
+	 * @throws Exception
+	 */
+
+	List<Version> versions(String bsn) throws Exception;
+
+	/**
+	 * @return The name of the repository
+	 */
+	String getName();
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/ResourceHandle.java b/bundleplugin/src/main/java/aQute/bnd/service/ResourceHandle.java
new file mode 100644
index 0000000..d08ea2c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/ResourceHandle.java
@@ -0,0 +1,13 @@
+package aQute.bnd.service;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface ResourceHandle {
+	
+	public enum Location { local, remote_cached, remote };
+	
+	String getName();
+	Location getLocation();
+	File request() throws IOException;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Scripter.java b/bundleplugin/src/main/java/aQute/bnd/service/Scripter.java
new file mode 100644
index 0000000..2e4e1d3
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Scripter.java
@@ -0,0 +1,10 @@
+package aQute.bnd.service;
+
+import java.io.*;
+import java.util.*;
+
+public interface Scripter {
+
+	void eval(Map<String, Object> x, StringReader stringReader);
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/SignerPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/SignerPlugin.java
new file mode 100644
index 0000000..aaef646
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/SignerPlugin.java
@@ -0,0 +1,15 @@
+package aQute.bnd.service;
+
+import aQute.lib.osgi.*;
+
+public interface SignerPlugin {
+    /**
+     * Sign the current jar. The alias is the given certificate 
+     * keystore.
+     * 
+     * @param builder   The current builder that contains the jar to sign
+     * @param alias     The keystore certificate alias
+     * @throws Exception When anything goes wrong
+     */
+    void sign(Builder builder, String alias) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/action/Action.java b/bundleplugin/src/main/java/aQute/bnd/service/action/Action.java
new file mode 100644
index 0000000..5167827
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/action/Action.java
@@ -0,0 +1,7 @@
+package aQute.bnd.service.action;
+
+import aQute.bnd.build.*;
+
+public interface Action {
+    void execute( Project project, String action) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/action/NamedAction.java b/bundleplugin/src/main/java/aQute/bnd/service/action/NamedAction.java
new file mode 100644
index 0000000..5a1c697
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/action/NamedAction.java
@@ -0,0 +1,6 @@
+package aQute.bnd.service.action;
+
+
+public interface NamedAction extends Action {
+    String getName();
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/action/packageinfo b/bundleplugin/src/main/java/aQute/bnd/service/action/packageinfo
new file mode 100644
index 0000000..ec0efd4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/action/packageinfo
@@ -0,0 +1 @@
+version 1.43.1
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/packageinfo b/bundleplugin/src/main/java/aQute/bnd/service/packageinfo
new file mode 100644
index 0000000..5035fd2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/packageinfo
@@ -0,0 +1 @@
+version 1.44.0
diff --git a/bundleplugin/src/main/java/aQute/bnd/settings/Settings.java b/bundleplugin/src/main/java/aQute/bnd/settings/Settings.java
new file mode 100644
index 0000000..78ed213
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/settings/Settings.java
@@ -0,0 +1,62 @@
+package aQute.bnd.settings;
+
+import java.security.*;
+import java.security.interfaces.*;
+import java.util.*;
+import java.util.prefs.*;
+
+import aQute.libg.cryptography.*;
+import aQute.libg.tuple.*;
+
+public class Settings {
+	public final static String	EMAIL			= "email";
+	public final static String	NAME			= "name";
+	public final static String	PASSWORD_SHA1	= "password.sha1";
+	final static String			KEY_PRIVATE		= "key.private";
+	final static String			KEY_PUBLIC		= "key.public";
+	final static String			KEY_SET			= "key.set";
+
+	static Preferences			prefs			= Preferences.userNodeForPackage(Settings.class);
+
+	public String globalGet(String key, String def) {
+		return prefs.get(key, def);
+	}
+
+	public void globalSet(String key, String value) throws BackingStoreException {
+		prefs.put(key, value);
+		prefs.sync();
+	}
+
+	public Collection<String> getKeys() throws BackingStoreException {
+		return Arrays.asList(prefs.keys());
+	}
+
+	public void globalRemove(String key) throws BackingStoreException {
+		prefs.remove(key);
+		prefs.sync();
+	}
+
+	private void generate() throws NoSuchAlgorithmException {
+		Pair<? extends PrivateKey, ? extends RSAPublicKey> pair = RSA.generate();
+		prefs.put(KEY_PRIVATE, Crypto.toString(pair.a));
+		prefs.put(KEY_PUBLIC, Crypto.toString(pair.b));
+		prefs.putBoolean(KEY_SET, true);
+	}
+
+	public PrivateKey getPrivateKey() throws Exception {
+		if (prefs.getBoolean(KEY_SET, false))
+			generate();
+
+		String key = prefs.get(KEY_PRIVATE, null);
+		return Crypto.fromString(key, PrivateKey.class);
+	}
+
+	public PublicKey getPublicKey() throws Exception {
+		if (prefs.getBoolean(KEY_SET, false))
+			generate();
+
+		String key = prefs.get(KEY_PUBLIC, null);
+		return Crypto.fromString(key, PublicKey.class);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/signing/JartoolSigner.java b/bundleplugin/src/main/java/aQute/bnd/signing/JartoolSigner.java
new file mode 100644
index 0000000..13e07cb
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/signing/JartoolSigner.java
@@ -0,0 +1,135 @@
+package aQute.bnd.signing;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.reporter.*;
+
+/**
+ * Sign the jar file.
+ * 
+ * -sign : <alias> [ ';' 'password:=' <password> ] [ ';' 'keystore:=' <keystore> ] [
+ * ';' 'sign-password:=' <pw> ] ( ',' ... )*
+ * 
+ * @author aqute
+ * 
+ */
+public class JartoolSigner implements Plugin, SignerPlugin {
+    String keystore;
+    String storetype;
+    String path = "jarsigner";
+    String storepass;
+    String keypass;
+    String sigFile;
+
+    public void setProperties(Map<String, String> map) {
+        if (map.containsKey("keystore"))
+            this.keystore = map.get("keystore");
+        if (map.containsKey("storetype"))
+            this.storetype = map.get("storetype");
+        if (map.containsKey("storepass"))
+            this.storepass = map.get("storepass");
+        if (map.containsKey("keypass"))
+            this.keypass = map.get("keypass");
+        if (map.containsKey("path"))
+            this.path = map.get("path");
+        if (map.containsKey("sigFile"))
+            this.sigFile = map.get("sigFile");
+
+    }
+
+    public void setReporter(Reporter processor) {
+    }
+
+    public void sign(Builder builder, String alias) throws Exception {    	    	
+    	File f = builder.getFile(keystore);
+    	if ( !f.isFile()) {
+    		builder.error("Invalid keystore %s", f.getAbsolutePath() );
+    		return;
+    	}
+    	
+        Jar jar = builder.getJar();
+        File tmp = File.createTempFile("signdjar", ".jar");
+        tmp.deleteOnExit();
+
+        jar.write(tmp);
+
+        Command command = new Command();
+        command.add(path);
+        if (keystore != null) {
+            command.add("-keystore");
+            command.add(f.getAbsolutePath());
+        }
+
+        if (storetype != null) {
+        	command.add("-storetype");
+        	command.add(storetype);
+        }
+
+        if (keypass != null) {
+        	command.add("-keypass");
+        	command.add(keypass);
+        }
+
+        if (storepass != null) {
+        	command.add("-storepass");
+        	command.add(storepass);
+        }
+
+        if (sigFile != null) {
+        	command.add("-sigFile");
+        	command.add(sigFile);
+        }
+
+        command.add(tmp.getAbsolutePath());
+        command.add(alias);
+        builder.trace("Jarsigner command: %s", command);
+        command.setTimeout(20, TimeUnit.SECONDS);
+        StringBuffer out = new StringBuffer();
+        StringBuffer err = new StringBuffer();
+        int exitValue = command.execute(System.in, out, err);
+        if (exitValue != 0) {
+            builder.error("Signing Jar out: %s\nerr: %s", out, err);
+        } else {
+            builder.trace("Signing Jar out: %s \nerr: %s", out, err);
+        }
+
+        Jar signed = new Jar(tmp);
+        builder.addClose(signed);
+
+        Map<String, Resource> dir = signed.getDirectories().get("META-INF");
+        for (String path : dir.keySet()) {
+            if (path.matches(".*\\.(DSA|RSA|SF|MF)$")) {
+                jar.putResource(path, dir.get(path));
+            }
+        }
+        jar.setDoNotTouchManifest();
+    }
+
+    StringBuffer collect(final InputStream in) throws Exception {
+        final StringBuffer sb = new StringBuffer();
+        
+        Thread tin = new Thread() {
+            public void run() {
+                try {
+                    BufferedReader rdr = new BufferedReader(new InputStreamReader(in, Constants.DEFAULT_CHARSET));
+                    String line = rdr.readLine();
+                    while (line != null) {
+                        sb.append(line);
+                        line = rdr.readLine();
+                    }
+                    rdr.close();
+                    in.close();
+                } catch (Exception e) {
+                    // Ignore any exceptions
+                }
+            }
+        };
+        tin.start();
+        return sb;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/signing/Signer.java b/bundleplugin/src/main/java/aQute/bnd/signing/Signer.java
new file mode 100644
index 0000000..bed91be
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/signing/Signer.java
@@ -0,0 +1,191 @@
+package aQute.bnd.signing;
+
+import java.io.*;
+import java.security.*;
+import java.security.KeyStore.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.base64.*;
+import aQute.lib.osgi.*;
+
+/**
+ * This class is used with the aQute.lib.osgi package, it signs jars with DSA
+ * signature.
+ * 
+ * -sign: md5, sha1
+ */
+public class Signer extends Processor {
+    static Pattern  METAINFDIR   = Pattern.compile("META-INF/[^/]*");
+    String         digestNames[]    = new String[] { "MD5" };
+    File           keystoreFile  = new File("keystore");
+    String         password;
+    String         alias;
+
+    public void signJar(Jar jar) {
+        if (digestNames == null || digestNames.length == 0)
+            error("Need at least one digest algorithm name, none are specified");
+
+        if (keystoreFile == null || !keystoreFile.getAbsoluteFile().exists()) {
+            error("No such keystore file: " + keystoreFile);
+            return;
+        }
+
+        if (alias == null) {
+            error("Private key alias not set for signing");
+            return;
+        }
+
+        MessageDigest digestAlgorithms[] = new MessageDigest[digestNames.length];
+
+        getAlgorithms(digestNames, digestAlgorithms);
+
+        try {
+            Manifest manifest = jar.getManifest();
+            manifest.getMainAttributes().putValue("Signed-By", "Bnd");
+
+            // Create a new manifest that contains the
+            // Name parts with the specified digests
+
+            ByteArrayOutputStream o = new ByteArrayOutputStream();
+            manifest.write(o);
+            doManifest(jar, digestNames, digestAlgorithms, o);
+            o.flush();
+            byte newManifestBytes[] = o.toByteArray();
+            jar.putResource("META-INF/MANIFEST.MF", new EmbeddedResource(
+                    newManifestBytes, 0));
+
+            // Use the bytes from the new manifest to create
+            // a signature file
+
+            byte[] signatureFileBytes = doSignatureFile(digestNames,
+                    digestAlgorithms, newManifestBytes);
+            jar.putResource("META-INF/BND.SF", new EmbeddedResource(
+                    signatureFileBytes, 0));
+
+            // Now we must create an RSA signature
+            // this requires the private key from the keystore
+
+            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+            KeyStore.PrivateKeyEntry privateKeyEntry = null;
+
+            try {
+                java.io.FileInputStream keystoreInputStream = new java.io.FileInputStream(
+                        keystoreFile);
+                keystore.load(keystoreInputStream, password == null ? null
+                        : password.toCharArray());
+                keystoreInputStream.close();
+                privateKeyEntry =  (PrivateKeyEntry) keystore.getEntry(
+                        alias, new KeyStore.PasswordProtection(password
+                                .toCharArray()));
+            } catch (Exception e) {
+                error("No able to load the private key from the give keystore("+keystoreFile.getAbsolutePath()+") with alias "+alias+" : "
+                        + e);
+                return;
+            }
+            PrivateKey privateKey = privateKeyEntry.getPrivateKey();
+
+            Signature signature = Signature.getInstance("MD5withRSA");
+            signature.initSign(privateKey);
+
+            signature.update(signatureFileBytes);
+
+            signature.sign();
+
+            // TODO, place the SF in a PCKS#7 structure ...
+            // no standard class for this? The following
+            // is an idea but we will to have do ASN.1 BER
+            // encoding ...
+
+         
+            
+            ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
+            jar.putResource("META-INF/BND.RSA", new EmbeddedResource(tmpStream
+                    .toByteArray(), 0));
+        } catch (Exception e) {
+            error("During signing: " + e);
+        }
+    }
+
+    private byte[] doSignatureFile(String[] digestNames,
+            MessageDigest[] algorithms, byte[] manbytes) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(out);
+        ps.print("Signature-Version: 1.0\r\n");
+
+        for (int a = 0; a < algorithms.length; a++) {
+            if (algorithms[a] != null) {
+                byte[] digest = algorithms[a].digest(manbytes);
+                ps.print(digestNames[a] + "-Digest-Manifest: ");
+                ps.print(new Base64(digest));
+                ps.print("\r\n");
+            }
+        }
+        return out.toByteArray();
+    }
+
+    private void doManifest(Jar jar, String[] digestNames,
+            MessageDigest[] algorithms, OutputStream out) throws Exception {
+
+        for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) {
+            String name = entry.getKey();
+            if (!METAINFDIR.matcher(name).matches()) {
+                out.write("\r\n".getBytes());
+                out.write("Name: ".getBytes());
+                out.write(name.getBytes());
+                out.write("\r\n".getBytes());
+
+                digest(algorithms, entry.getValue());
+                for (int a = 0; a < algorithms.length; a++) {
+                    if (algorithms[a] != null) {
+                        byte[] digest = algorithms[a].digest();
+                        String header = digestNames[a] + "-Digest: "
+                                + new Base64(digest) + "\r\n";
+                        out.write(header.getBytes());
+                    }
+                }
+            }
+        }
+    }
+
+    private void digest(MessageDigest[] algorithms, Resource r)
+            throws Exception {
+        InputStream in = r.openInputStream();
+        byte[] data = new byte[1024];
+        int size = in.read(data);
+        while (size > 0) {
+            for (int a = 0; a < algorithms.length; a++) {
+                if (algorithms[a] != null) {
+                    algorithms[a].update(data, 0, size);
+                }
+            }
+            size = in.read(data);
+        }
+    }
+
+    private void getAlgorithms(String[] digestNames, MessageDigest[] algorithms) {
+        for (int i = 0; i < algorithms.length; i++) {
+            String name = digestNames[i];
+            try {
+                algorithms[i] = MessageDigest.getInstance(name);
+            } catch (NoSuchAlgorithmException e) {
+                error("Specified digest algorithm " + digestNames[i]
+                        + ", but not such algorithm was found: " + e);
+            }
+        }
+    }
+
+    public void setPassword(String string) {
+        password = string;
+    }
+
+    public void setKeystore(File keystore) {
+        this.keystoreFile = keystore;
+    }
+
+    public void setAlias(String string) {
+        this.alias = string;
+    }
+}