Use local copy of latest bndlib code for pre-release testing purposes

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1347815 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/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..ecaaa7b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/annotation/metatype/Configurable.java
@@ -0,0 +1,294 @@
+package aQute.bnd.annotation.metatype;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+@SuppressWarnings( { "unchecked", "rawtypes" }) 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) {
+				Class<?> rt = method.getReturnType();
+				if ( rt == boolean.class || rt==Boolean.class)
+					return false;
+				
+				if (method.getReturnType().isPrimitive()) {
+
+					o = "0";
+				} else
+					return null;
+			}
+
+			return convert(method.getGenericReturnType(), o);
+		}
+
+		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);
+				}
+				
+				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());
+			}
+			if (Map.class.isAssignableFrom(resultType)){
+				Map<?,?> input = toMap(o);
+				if (resultType.isInterface()) {
+					if (resultType == SortedMap.class)
+						resultType = TreeMap.class;
+					else if (resultType == Map.class)
+						resultType = LinkedHashMap.class;
+					else
+						throw new IllegalArgumentException(
+								"Unknown interface for a collection, no concrete class found: "
+										+ resultType);
+				}
+				Map<Object,Object> result = (Map<Object,Object>) resultType
+						.newInstance();
+				Type keyType = pType.getActualTypeArguments()[0];
+				Type valueType = pType.getActualTypeArguments()[1];
+
+				for ( Map.Entry<?, ?> entry : input.entrySet()) {
+					result.put(convert(keyType,entry.getKey()), convert(valueType,entry.getValue()));
+				}
+				return result;
+			}
+			throw new IllegalArgumentException("cannot convert to " + pType
+					+ " because it uses generics and is not a Collection or a map");
+		}
+
+
+		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);
+		}
+		
+		private Map<?, ?> toMap(Object o) {
+			if ( o instanceof Map) 
+				return (Map<?, ?>) o;
+			
+			throw new IllegalArgumentException(
+					"Cannot convert " + o + " to a map as requested");
+		}
+
+
+	}
+	
+	
+	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..2fb38d9
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Container.java
@@ -0,0 +1,210 @@
+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, PROJECT_BUNDLE, 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 = null;
+			BufferedReader rd = null;
+			String line;
+			try {
+				in = new FileInputStream(file);
+				rd = new BufferedReader(new InputStreamReader(in,
+						Constants.DEFAULT_CHARSET));
+				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 {
+				if (rd != null) {
+					rd.close();
+				}
+				if (in != null) {
+					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..034a364
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Project.java
@@ -0,0 +1,2092 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+import java.util.jar.*;
+
+import aQute.bnd.help.*;
+import aQute.bnd.maven.support.*;
+import aQute.bnd.service.*;
+import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.bnd.service.action.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.eclipse.*;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.sed.*;
+import aQute.libg.version.*;
+
+/**
+ * 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>	classpath				= new LinkedHashSet<Container>();
+	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[];
+	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 {
+						Container c = new Container(this, output);
+						if (!buildpath.contains(c))
+							buildpath.add(c);
+					}
+
+					// 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 = new Parameters(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 " + p + " 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>();
+		Parameters bundles = new Parameters(spec);
+
+		try {
+			for (Iterator<Entry<String, Attrs>> i = bundles.entrySet().iterator(); i.hasNext();) {
+				Entry<String, Attrs> entry = i.next();
+				String bsn = entry.getKey();
+				Map<String, String> attrs = entry.getValue();
+
+				Container found = null;
+
+				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, versionRange,
+									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;
+	}
+
+	/**
+	 * Just calls a new method with a default parm.
+	 * 
+	 * @throws Exception
+	 * 
+	 */
+	Collection<Container> getBundles(Strategy strategy, String spec) throws Exception {
+		return getBundles(strategy, spec, null);
+	}
+
+	/**
+	 * 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, Attrs>> queue = new LinkedList<Map.Entry<String, Attrs>>();
+		queue.addAll(new Parameters(spec).entrySet());
+
+		while (!queue.isEmpty()) {
+			Entry<String, Attrs> 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, new Parameters(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 {
+		trace("release %s", name);
+		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 {
+				File file = rp.put(jar);
+				trace("Released %s to file %s in repository %s", jar.getName(), file, rp);
+			} catch (Exception e) {
+				error("Deploying " + jar.getName() + " on " + rp.getName(), e);
+			} finally {
+				jar.close();
+			}
+		} else if (name == null)
+			error("There is no writable repository (no repo name specified)");
+		else
+			error("Cannot find a writeable repository with the name %s from the repositiories %s",
+					name, plugins);
+
+		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 {
+		trace("release");
+		File[] jars = build(test);
+		// If build fails jars will be null
+		if (jars == null) {
+			trace("no jars being build");
+			return;
+		}
+		trace("build ", Arrays.toString(jars));
+		for (File jar : jars) {
+			Jar j = new Jar(jar);
+			try {
+				release(name, j);
+			} finally {
+				j.close();
+			}
+		}
+
+	}
+
+	/**
+	 * Get a bundle from one of the plugin repositories. If an exact version is
+	 * required we just return the first repository found (in declaration order
+	 * in the build.bnd file).
+	 * 
+	 * @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 strategy,
+			Map<String, String> attrs) throws Exception {
+
+		if (range == null)
+			range = "0";
+
+		if ("snapshot".equals(range)) {
+			return getBundleFromProject(bsn, attrs);
+		}
+
+		Strategy useStrategy = strategy;
+
+		if ("latest".equals(range)) {
+			Container c = getBundleFromProject(bsn, attrs);
+			if (c != null)
+				return c;
+
+			useStrategy = Strategy.HIGHEST;
+		}
+
+		useStrategy = overrideStrategy(attrs, useStrategy);
+
+		List<RepositoryPlugin> plugins = workspace.getRepositories();
+
+		if (useStrategy == Strategy.EXACT) {
+
+			// For an exact range we just iterate over the repos
+			// and return the first we find.
+
+			for (RepositoryPlugin plugin : plugins) {
+				File result = plugin.get(bsn, range, Strategy.EXACT, attrs);
+				if (result != null)
+					return toContainer(bsn, range, attrs, result);
+			}
+		} else {
+			VersionRange versionRange = "latest".equals(range) ? new VersionRange("0")
+					: new VersionRange(range);
+
+			// We have a range search. Gather all the versions in all the repos
+			// and make a decision on that choice. If the same version is found
+			// in
+			// multiple repos we take the first
+
+			SortedMap<Version, RepositoryPlugin> versions = new TreeMap<Version, RepositoryPlugin>();
+			for (RepositoryPlugin plugin : plugins) {
+				try {
+					List<Version> vs = plugin.versions(bsn);
+					if (vs != null) {
+						for (Version v : vs) {
+							if (!versions.containsKey(v) && versionRange.includes(v))
+								versions.put(v, plugin);
+						}
+					}
+				} catch (UnsupportedOperationException ose) {
+					// We have a plugin that cannot list versions, try
+					// if it has this specific version
+					// The main reaosn for this code was the Maven Remote
+					// Repository
+					// To query, we must have a real version
+					if (!versions.isEmpty() && Verifier.isVersion(range)) {
+						File file = plugin.get(bsn, range, useStrategy, attrs);
+						// and the entry must exist
+						// if it does, return this as a result
+						if (file != null)
+							return toContainer(bsn, range, attrs, file);
+					}
+				}
+			}
+
+			// Verify if we found any, if so, we use the strategy to pick
+			// the first or last
+
+			if (!versions.isEmpty()) {
+				Version provider = null;
+
+				switch (useStrategy) {
+				case HIGHEST:
+					provider = versions.lastKey();
+					break;
+
+				case LOWEST:
+					provider = versions.firstKey();
+					break;
+				}
+				if (provider != null) {
+					RepositoryPlugin repo = versions.get(provider);
+					String version = provider.toString();
+					File result = repo.get(bsn, version, Strategy.EXACT, attrs);
+					if (result != null)
+						return toContainer(bsn, version, attrs, result);
+				} else
+					error("Unexpected, found versions %s but then not provider for startegy %s?",
+							versions, useStrategy);
+			}
+		}
+
+		//
+		// If we get this far we ran into an error somewhere
+
+		return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version="
+				+ range + " Not found in " + plugins, null);
+
+	}
+
+	/**
+	 * @param attrs
+	 * @param useStrategy
+	 * @return
+	 */
+	protected Strategy overrideStrategy(Map<String, String> attrs, Strategy useStrategy) {
+		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;
+			}
+		}
+		return useStrategy;
+	}
+
+	/**
+	 * @param bsn
+	 * @param range
+	 * @param attrs
+	 * @param result
+	 * @return
+	 */
+	protected Container toContainer(String bsn, String range, Map<String, String> attrs, File result) {
+		File f = result;
+		if (f == null) {
+			error("Result file for toContainer is unexpectedly null, not sure what to do");
+			f = new File("was null");
+		}
+		if (f.getName().endsWith("lib"))
+			return new Container(this, bsn, range, Container.TYPE.LIBRARY, f, null, attrs);
+		else
+			return new Container(this, bsn, range, Container.TYPE.REPO, f, null, attrs);
+	}
+
+	/**
+	 * 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 {
+		Parameters deploy = new Parameters(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 (isNoBundles())
+			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. This is defined as:
+	 * 
+	 */
+	public boolean isStale() throws Exception {
+		// When we do not generate anything ...
+		if (isNoBundles())
+			return false;
+
+		long buildTime = 0;
+
+		files = getBuildFiles(false);
+		if (files == null)
+			return true;
+
+		for (File f : files) {
+			if (f.lastModified() < lastModified())
+				return true;
+
+			if (buildTime < f.lastModified())
+				buildTime = f.lastModified();
+		}
+
+		for (Project dependency : getDependson()) {
+			if (dependency.isStale())
+				return true;
+
+			if (dependency.isNoBundles())
+				continue;
+
+			File[] deps = dependency.getBuildFiles();
+			for (File f : deps) {
+				if (f.lastModified() >= buildTime)
+					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.
+	 * 
+	 * This method remembers the files in the appropriate instance vars.
+	 * 
+	 * @return
+	 */
+
+	public File[] getBuildFiles() throws Exception {
+		return getBuildFiles(true);
+	}
+
+	public File[] getBuildFiles(boolean buildIfAbsent) throws Exception {
+		if (files != null)
+			return files;
+
+		File f = new File(getTarget(), BUILDFILES);
+		if (f.isFile()) {
+			BufferedReader rdr = IO.reader(f);
+			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()) {
+						// Originally we warned the user
+						// but lets just rebuild. That way
+						// the error is not noticed but 
+						// it seems better to correct,
+						// See #154
+						rdr.close();
+						f.delete();
+						break;
+					} else
+						files.add(ff);
+				}
+				return this.files = files.toArray(new File[files.size()]);
+			} finally {
+				rdr.close();
+			}
+		}
+		if (buildIfAbsent)
+			return files = buildLocal(false);
+		else
+			return files = 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 (isNoBundles())
+			return null;
+
+		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
+			Writer fw = IO.writer(bfs);
+			try {
+				for (File f : files) {
+					fw.append(f.getAbsolutePath());
+					fw.append("\n");
+				}
+			} finally {
+				fw.close();
+			}
+			getWorkspace().changedFile(bfs);
+			return files;
+		} else
+			return null;
+	}
+
+	/**
+	 * Answer if this project does not have any output
+	 * 
+	 * @return
+	 */
+	public boolean isNoBundles() {
+		return getProperty(NOBUNDLES) != 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();
+		}
+	}
+
+	public 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);
+
+		Parameters actions = new Parameters(getProperty("-actions", DEFAULT_ACTIONS));
+		for (Entry<String, Attrs> 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);
+			target.mkdirs();
+		}
+		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.err.println("No Errors");
+		} else {
+			if (errors > 0) {
+				System.err.println(errors + " Error(s)");
+
+			} else
+				System.err.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();
+	}
+
+	/**
+	 * Bump the version of this project. First check the main bnd file. If this
+	 * does not contain a version, check the include files. If they still do not
+	 * contain a version, then check ALL the sub builders. If not, add a version
+	 * to the main bnd file.
+	 * 
+	 * @param mask
+	 *            the mask for bumping, see {@link Macro#_version(String[])}
+	 * @throws Exception
+	 */
+	public void bump(String mask) throws Exception {
+		String pattern = "(Bundle-Version\\s*(:|=)\\s*)(([0-9]+(\\.[0-9]+(\\.[0-9]+)?)?))";
+		String replace = "$1${version;" + mask + ";$3}";
+		try {
+			// First try our main bnd file
+			if (replace(getPropertiesFile(), pattern, replace))
+				return;
+
+			trace("no version in bnd.bnd");
+
+			// Try the included filed in reverse order (last has highest
+			// priority)
+			List<File> included = getIncluded();
+			if (included != null) {
+				List<File> copy = new ArrayList<File>(included);
+				Collections.reverse(copy);
+
+				for (File file : copy) {
+					if (replace(file, pattern, replace)) {
+						trace("replaced version in file %s", file);
+						return;
+					}
+				}
+			}
+			trace("no version in included files");
+
+			boolean found = false;
+
+			// Replace in all sub builders.
+			for (Builder sub : getSubBuilders()) {
+				found |= replace(sub.getPropertiesFile(), pattern, replace);
+			}
+
+			if (!found) {
+				trace("no version in sub builders, add it to bnd.bnd");
+				String bndfile = IO.collect(getPropertiesFile());
+				bndfile += "\n# Added by by bump\nBundle-Version: 0.0.0\n";
+				IO.store(bndfile, getPropertiesFile());
+			}
+		} finally {
+			forceRefresh();
+		}
+	}
+
+	boolean replace(File f, String pattern, String replacement) throws IOException {
+		Sed sed = new Sed(getReplacer(), f);
+		sed.replace(pattern, replacement);
+		return sed.doIt() > 0;
+	}
+
+	public void bump() throws Exception {
+		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, "", new Instruction(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() {
+		Parameters hdr = getParameters(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));
+		trace("candidates for tester %s", withDefault);
+
+		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.err.printf("Could not acquire lock for %s, was locked by %s for %s%n", reason,
+					lockingThread, lockingReason);
+			System.err.flush();
+			return false;
+		}
+		this.lockingReason = reason;
+		this.lockingThread = Thread.currentThread();
+		return true;
+	}
+
+	public void unlock() {
+		lockingReason = null;
+		lock.unlock();
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * Sets the package version on an exported package
+	 * 
+	 * @param packageName
+	 *            The package name
+	 * @param version
+	 *            The new package version
+	 */
+	public void setPackageInfo(String packageName, Version version) {
+		try {
+			updatePackageInfoFile(packageName, version);
+		} catch (Exception e) {
+			error(e.getMessage(), e);
+		}
+	}
+
+	void updatePackageInfoFile(String packageName, Version newVersion) throws Exception {
+
+		File file = getPackageInfoFile(packageName);
+
+		// If package/classes are copied into the bundle through Private-Package
+		// etc, there will be no source
+		if (!file.getParentFile().exists()) {
+			return;
+		}
+
+		Version oldVersion = getPackageInfo(packageName);
+
+		if (newVersion.compareTo(oldVersion) == 0) {
+			return;
+		} else {
+			PrintWriter pw = IO.writer(file);
+			pw.println("version " + newVersion);
+			pw.flush();
+			pw.close();
+
+			String path = packageName.replace('.', '/') + "/packageinfo";
+			File binary = IO.getFile(getOutput(), path);
+			binary.getParentFile().mkdirs();
+			IO.copy(file, binary);
+
+			refresh();
+		}
+	}
+
+	File getPackageInfoFile(String packageName) throws IOException {
+		String path = packageName.replace('.', '/') + "/packageinfo";
+		return IO.getFile(getSrc(), path);
+
+	}
+
+	public Version getPackageInfo(String packageName) throws IOException {
+		File packageInfoFile = getPackageInfoFile(packageName);
+		if (!packageInfoFile.exists()) {
+			return Version.emptyVersion;
+		}
+		BufferedReader reader = null;
+		try {
+			reader = IO.reader(packageInfoFile);
+			String line;
+			while ((line = reader.readLine()) != null) {
+				line = line.trim();
+				if (line.startsWith("version ")) {
+					return Version.parseVersion(line.substring(8));
+				}
+			}
+		} finally {
+			if (reader != null) {
+				IO.close(reader);
+			}
+		}
+		return Version.emptyVersion;
+	}
+
+	/**
+	 * bnd maintains a class path that is set by the environment, i.e. bnd is
+	 * not in charge of it.
+	 */
+	
+	public void addClasspath( File f) {
+		if ( !f.isFile()) {
+			error("Adding non existent file to the classpath %s", f);
+		}
+		Container container = new Container(f);
+		classpath.add(container);
+	}
+	
+	public void clearClasspath() {
+		classpath.clear();
+	}
+	
+	public Collection<Container> getClasspath() {
+		return classpath;
+	}
+}
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..d00b71b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
@@ -0,0 +1,77 @@
+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.getClasspath()) {
+                    addClasspath(file.getFile());
+                }
+                
+                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..1b7bac0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
@@ -0,0 +1,371 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+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.*;
+import aQute.libg.header.*;
+
+/**
+ * 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 List<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 Parameters					runsystempackages;
+	private final List<String>			activators			= Create.list();
+	private File						storageDir;
+	private final List<String>			warnings			= Create.list();
+	private final List<String>			errors				= Create.list();
+
+	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());
+			} else {
+				warning("Bundle file \"%s\" does not exist", file);
+			}
+		}
+
+		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.getParameters(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) {
+						Parameters exports = project.parseHeader(manifest.getMainAttributes()
+								.getValue(Constants.EXPORT_PACKAGE));
+						for (Entry<String, Attrs> 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 List<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);
+
+		try {
+			int result = java.execute((InputStream)null, System.err, System.err);
+			if (result == Integer.MIN_VALUE)
+				return TIMEDOUT;
+			reportResult(result);
+			return result;
+		} finally {
+			cleanup();
+		}
+	}
+
+	/**
+	 * Is called after the process exists. Can you be used to cleanup
+	 * the properties file.
+	 */
+	
+	public void cleanup() {
+		// do nothing by default
+	}
+
+	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.error("Exit code remote process %d: %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, ? extends Map<String, String>> getSystemPackages() {
+		return runsystempackages.asMapMap();
+	}
+
+	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();
+	}
+
+	public void clear() {
+		errors.clear();
+		warnings.clear();
+	}
+
+	public List<String> getErrors() {
+		return Collections.unmodifiableList(errors);
+	}
+
+	public List<String> getWarnings() {
+		return Collections.unmodifiableList(warnings);
+	}
+
+	protected void error(String message, Object... args) {
+		String formatted = String.format(message, args);
+		errors.add(formatted);
+	}
+
+	protected void warning(String message, Object... args) {
+		String formatted = String.format(message, args);
+		warnings.add(formatted);
+	}
+}
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..44a24de
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
@@ -0,0 +1,354 @@
+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.*;
+import aQute.libg.reporter.*;
+
+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 File									buildDir;
+	final Maven									maven		= new Maven(Processor.getExecutor());
+
+	/**
+	 * 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();
+
+	}
+
+	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) {
+				String extensionName = extension.getName();
+				if (extensionName.endsWith(".bnd")) {
+					extensionName = extensionName.substring(0, extensionName.length() - ".bnd".length());
+					try {
+						doIncludeFile(extension, false, getProperties(), "ext." + extensionName);
+					} 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?
+			}
+	}
+
+	
+	/**
+	 * Signal a BndListener plugin.
+	 * We ran an infinite bug loop :-( 
+	 */
+	final ThreadLocal<Reporter> signalBusy = new ThreadLocal<Reporter>();
+	public void signal(Reporter reporter) {
+		if ( signalBusy.get() != null)
+			return;
+		
+		signalBusy.set(reporter);
+		try {
+			List<BndListener> listeners = getPlugins(BndListener.class);
+			for (BndListener l : listeners)
+				try {
+					l.signal(this);
+				} catch (Exception e) {
+					// who cares?
+				}
+		} catch (Exception e) {
+			// Ignore
+		} finally {
+			signalBusy.set(null);
+		}
+	}
+
+	@Override public void signal() {
+		signal(this);
+	}
+
+	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);
+		}
+		
+		public String toString() {
+			return "bnd-cache";
+		}
+
+		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.err.println("!!!! Couldn't find embedded-repo.jar in bundle ");
+						error("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 Collection<Project> getBuildOrder() throws Exception {
+		List<Project> result = new ArrayList<Project>();
+		for (Project project : getAllProjects()) {
+			Collection<Project> dependsOn = project.getDependson();
+			getBuildOrder(dependsOn, result);
+			if (!result.contains(project)) {
+				result.add(project);
+			}
+		}
+		return result;
+	}
+
+	private void getBuildOrder(Collection<Project> dependsOn, List<Project> result) throws Exception {
+		for (Project project : dependsOn) {
+			Collection<Project> subProjects = project.getDependson();
+			for (Project subProject : subProjects) {
+				if (!result.contains(subProject)) {
+					result.add(subProject);
+				}
+			}
+			if (!result.contains(project)) {
+				result.add(project);
+			}
+		}
+	}
+
+	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(new CachedFileRepo());
+	}
+
+}
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..5035fd2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/packageinfo
@@ -0,0 +1 @@
+version 1.44.0
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..e187f3c
--- /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)
+			this.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..f1f91d1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/compatibility/ParseSignatureBuilder.java
@@ -0,0 +1,119 @@
+package aQute.bnd.compatibility;
+
+import java.io.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Descriptors.TypeRef;
+
+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 Exception {
+		Analyzer analyzer = new Analyzer();
+		Clazz clazz = new Clazz(analyzer, "", null);
+		
+		clazz.parseClassFile(in, new ClassDataCollector() {
+			Scope	s;
+			Scope	enclosing;
+			Scope	declaring;
+
+			@Override
+			public void classBegin(int access, TypeRef name) {
+				s = root.getScope(name.getBinary());
+				s.access = Access.modifier(access);
+				s.kind = Kind.CLASS;
+			}
+
+			@Override
+			public void extendsClass(TypeRef name) {
+//				s.setBase(new GenericType(name));
+			}
+
+			@Override
+			public void implementsInterfaces(TypeRef names[]) {
+				s.setParameterTypes(convert(names));
+			}
+
+			GenericType[] convert(TypeRef names[]) {
+				GenericType tss[] = new GenericType[names.length];
+				for (int i = 0; i < names.length; i++) {
+//					tss[i] = new GenericType(names[i]);
+				}
+				return tss;
+			}
+
+			@Override
+			public void method(Clazz.MethodDef defined) {
+				String descriptor;
+				Kind kind;
+				if (defined.isConstructor()) {
+					descriptor = ":" + defined.getDescriptor();
+					kind = Kind.CONSTRUCTOR;
+				} else {
+					descriptor = defined.getName() + ":" + defined.getDescriptor();
+					kind = Kind.METHOD;
+				}
+				Scope m = s.getScope(descriptor);
+				m.access = Access.modifier(defined.getAccess());
+				m.kind = kind;
+				m.declaring = s;
+				s.add(m);
+			}
+
+			@Override
+			public void field(Clazz.FieldDef defined) {
+				String descriptor = defined.getName() + ":" + defined.getDescriptor();
+				Kind kind = Kind.FIELD;
+				Scope m = s.getScope(descriptor);
+				m.access = Access.modifier(defined.getAccess());
+				m.kind = kind;
+				m.declaring = s;
+				s.add(m);
+			}
+
+			@Override
+			public void classEnd() {
+				if (enclosing != null)
+					s.setEnclosing( enclosing );
+				if (declaring != null)
+					s.setDeclaring( declaring );				
+			}
+
+			@Override
+			public void enclosingMethod(TypeRef cName, String mName, String mDescriptor) {
+				enclosing = root.getScope(cName.getBinary());
+				if (mName != null) {
+					enclosing = enclosing.getScope(Scope.methodIdentity(mName, mDescriptor));
+				}
+			}
+
+			@Override
+			public void innerClass(TypeRef innerClass, TypeRef outerClass, String innerName,
+					int innerClassAccessFlags) {
+				if (outerClass != null && innerClass != null && innerClass.getBinary().equals(s.name))
+					declaring = root.getScope(outerClass.getBinary());
+			}
+		});
+		
+		
+	}
+}
+
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..32ae94f
--- /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..7f22f35
--- /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 name2) {
+		return name2.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..9b31f7f
--- /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.err;
+//
+//		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..2b9ce53
--- /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.
+	 */
+	static 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 {
+		StringBuilder sb = new StringBuilder();
+		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 {
+		StringBuilder sb = new StringBuilder();
+		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 {
+		StringBuilder sb = new StringBuilder();
+		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 {
+		StringBuilder sb = new StringBuilder();
+		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(StringBuilder 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(StringBuilder 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(StringBuilder 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) {
+		StringBuilder sb = new StringBuilder();
+		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(StringBuilder 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(StringBuilder 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(StringBuilder 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..35cec3f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/AnnotationReader.java
@@ -0,0 +1,353 @@
+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.lib.osgi.Clazz.MethodDef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.version.*;
+
+/**
+ * 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
+ */
+public class AnnotationReader extends ClassDataCollector {
+	final static TypeRef[]		EMPTY					= new TypeRef[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				BINDNAME				= Pattern.compile("(set|add|bind)?(.*)");
+	static Pattern				BINDDESCRIPTOR			= Pattern
+																.compile("\\(((L([^;]+);)(Ljava/util/Map;)?|Lorg/osgi/framework/ServiceReference;)\\)V");
+
+	static Pattern				LIFECYCLEDESCRIPTOR		= 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;
+	TypeRef						interfaces[];
+	MethodDef					method;
+	TypeRef						className;
+	Analyzer					analyzer;
+	MultiMap<String, String>	methods					= new MultiMap<String, String>();
+	TypeRef						extendsClass;
+	boolean						inherit;
+	boolean						baseclass				= true;
+
+	AnnotationReader(Analyzer analyzer, Clazz clazz, boolean inherit) {
+		this.analyzer = analyzer;
+		this.clazz = clazz;
+		this.inherit = inherit;
+	}
+
+	public static ComponentDef getDefinition(Clazz c, Analyzer analyzer) throws Exception {
+		boolean inherit = Processor.isTrue(analyzer.getProperty("-dsannotations-inherit"));
+		AnnotationReader r = new AnnotationReader(analyzer, c, inherit);
+		return r.getDef();
+	}
+
+	private ComponentDef getDef() throws Exception {
+		clazz.parseClassFileWithCollector(this);
+		if (component.implementation == null)
+			return null;
+
+		if (inherit) {
+			baseclass = false;
+			while (extendsClass != null) {
+				if (extendsClass.isJava())
+					break;
+
+				Clazz ec = analyzer.findClass(extendsClass);
+				if (ec == null) {
+					analyzer.error("Missing super class for DS annotations: " + extendsClass
+							+ " from " + clazz.getClassName());
+				} else {
+					ec.parseClassFileWithCollector(this);
+				}
+			}
+		}
+		for (ReferenceDef rdef : component.references.values()) {
+			rdef.unbind = referredMethod(analyzer, rdef, rdef.unbind, "add(.*)", "remove$1",
+					"(.*)", "un$1");
+			rdef.updated = referredMethod(analyzer, rdef, rdef.updated, "(add|set|bind)(.*)",
+					"updated$2", "(.*)", "updated$1");
+		}
+		return component;
+	}
+
+	/**
+	 * 
+	 * @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.service.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.service);
+		}
+		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 (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+			analyzer.error(
+					"Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, method.getDescriptor());
+		else {
+			component.deactivate = method.getName();
+		}
+	}
+
+	/**
+	 * 
+	 */
+	protected void doModified() {
+		if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+			analyzer.error(
+					"Modified method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, method.getDescriptor());
+		else {
+			component.modified = method.getName();
+		}
+	}
+
+	/**
+	 * @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 = BINDNAME.matcher(method.getName());
+			if ( m.matches() )
+				def.name = m.group(2);
+			else
+				analyzer.error("Invalid name for bind method %s", method.getName());
+		}
+
+		def.unbind = reference.unbind();
+		def.updated = reference.updated();
+		def.bind = method.getName();
+
+		def.service = raw.get("service");
+		if (def.service != null) {
+			def.service = Clazz.objectDescriptorToFQN(def.service);
+		} else {
+			// We have to find the type of the current method to
+			// link it to the referenced service.
+			Matcher m = BINDDESCRIPTOR.matcher(method.getDescriptor().toString());
+			if (m.matches()) {
+				def.service = Descriptors.binaryToFQN(m.group(3));
+			} else
+				throw new IllegalArgumentException(
+						"Cannot detect the type of a Component Reference from the descriptor: "
+								+ method.getDescriptor());
+		}
+
+		// Check if we have a target, this must be a filter
+		def.target = reference.target();
+
+		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.service, "");
+		else
+			component.references.put(def.name, def);
+
+		def.cardinality = reference.cardinality();
+		def.policy = reference.policy();
+		def.policyOption = reference.policyOption();
+	}
+
+	/**
+	 * 
+	 */
+	protected void doActivate() {
+		if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+			analyzer.error(
+					"Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, method.getDescriptor());
+		else {
+			component.activate = method.getName();
+		}
+	}
+
+	/**
+	 * @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.getClassName();
+		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();
+
+		if (annotation.get("configurationPid") != null)
+			component.configurationPid = comp.configurationPid();
+
+		if (annotation.get("xmlns") != null)
+			component.xmlns = comp.xmlns();
+
+		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<TypeRef> result = new ArrayList<TypeRef>();
+				for (int i = 0; i < interfaces.length; i++) {
+					if (!interfaces[i].equals(analyzer.getTypeRef("scala/ScalaObject")))
+						result.add(interfaces[i]);
+				}
+				component.service = result.toArray(EMPTY);
+			}
+		} else {
+			// We have explicit interfaces set
+			component.service = new TypeRef[x.length];
+			for (int i = 0; i < x.length; i++) {
+				String s = (String) x[i];
+				TypeRef ref = analyzer.getTypeRefFromFQN(s);
+				component.service[i] = ref;
+			}
+		}
+
+	}
+
+	/**
+	 * 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, TypeRef name) {
+		className = name;
+	}
+
+	@Override public void implementsInterfaces(TypeRef[] interfaces) {
+		this.interfaces = interfaces;
+	}
+
+	@Override public void method(Clazz.MethodDef method) {
+		int access = method.getAccess();
+
+		if (Modifier.isAbstract(access) || Modifier.isStatic(access))
+			return;
+
+		if (!baseclass && Modifier.isPrivate(access))
+			return;
+
+		this.method = method;
+		methods.add(method.getName(), method.getDescriptor().toString());
+	}
+
+	@Override public void extendsClass(TypeRef 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..75b7b73
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
@@ -0,0 +1,208 @@
+package aQute.bnd.component;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+/**
+ * This class just holds the information for the component, implementation, and
+ * service/provide elements. The {@link #prepare(Analyzer)} method will check if
+ * things are ok and the {@link #getTag()} method returns a tag if the prepare
+ * method returns without any errors. The class uses {@link ReferenceDef} to
+ * hold the references.
+ */
+class ComponentDef {
+	final static String				NAMESPACE_STEM	= "http://www.osgi.org/xmlns/scr";
+	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>();
+
+	Version							version			= AnnotationReader.V1_1;
+	String							name;
+	String							factory;
+	Boolean							immediate;
+	Boolean							servicefactory;
+	ConfigurationPolicy				configurationPolicy;
+	TypeRef							implementation;
+	TypeRef							service[];
+	String							activate;
+	String							deactivate;
+	String							modified;
+	Boolean							enabled;
+	String							xmlns;
+	String							configurationPid;
+	List<Tag>						propertyTags	= new ArrayList<Tag>();
+
+	/**
+	 * Called to prepare. If will look for any errors or inconsistencies in the
+	 * setup.
+	 * 
+	 * @param analyzer
+	 *            the analyzer to report errors and create references
+	 * @throws Exception
+	 */
+	void prepare(Analyzer analyzer) throws Exception {
+
+		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);
+			return;
+		}
+
+		analyzer.referTo(implementation);
+
+		if (name == null)
+			name = implementation.getFQN();
+
+		if (service != null && service.length > 0) {
+			for (TypeRef 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");
+
+		if (configurationPid != null)
+			version = ReferenceDef.max(version, AnnotationReader.V1_2);
+
+		for (Map.Entry<String, List<String>> kvs : property.entrySet()) {
+			Tag property = new Tag("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);
+			}
+			if (kvs.getValue().size() == 1) {
+				String value = kvs.getValue().get(0);
+				value = check(type, value, analyzer);
+				property.addAttribute("value", value);
+			} else {
+				StringBuilder sb = new StringBuilder();
+
+				String del = "";
+				for (String v : kvs.getValue()) {
+					sb.append(del);
+					v = check(type, v, analyzer);
+					sb.append(v);
+					del = "\n";
+				}
+				property.addContent(sb.toString());
+			}
+			propertyTags.add(property);
+		}
+	}
+
+	/**
+	 * Returns a tag describing the component element.
+	 * 
+	 * @return a component element
+	 */
+	Tag getTag() {
+		Tag component = new Tag("scr:component");
+		if (xmlns != null)
+			component.addAttribute("xmlns:scr", xmlns);
+		else
+			component.addAttribute("xmlns:scr", NAMESPACE_STEM + "/v" + 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);
+
+		if (configurationPid != null)
+			component.addAttribute("configuration-pid", configurationPid);
+
+		Tag impl = new Tag(component, "implementation");
+		impl.addAttribute("class", implementation.getFQN());
+
+		if (service != null && service.length != 0) {
+			Tag s = new Tag(component, "service");
+			if (servicefactory != null && servicefactory)
+				s.addAttribute("servicefactory", true);
+
+			for (TypeRef ss : service) {
+				Tag provide = new Tag(s, "provide");
+				provide.addAttribute("interface", ss.getFQN());
+			}
+		}
+
+		for (ReferenceDef ref : references.values()) {
+			Tag refTag = ref.getTag();
+			component.addContent(refTag);
+		}
+
+		for (Tag tag : propertyTags)
+			component.addContent(tag);
+
+		for (String entry : properties) {
+			Tag properties = new Tag(component, "properties");
+			properties.addAttribute("entry", entry);
+		}
+		return component;
+	}
+
+	private String check(String type, String v, Analyzer analyzer) {
+		if (type == null)
+			return v;
+
+		try {
+			Class<?> c = Class.forName("java.lang." + type);
+			if (c == String.class)
+				return v;
+
+			v = v.trim();
+			if (c == Character.class)
+				c = Integer.class;
+			Method m = c.getMethod("valueOf", String.class);
+			m.invoke(null, v);
+		} catch (ClassNotFoundException e) {
+			analyzer.error("Invalid data type %s", type);
+		} catch (NoSuchMethodException e) {
+			analyzer.error("Cannot convert data %s to type %s", v, type);
+		} catch (NumberFormatException e) {
+			analyzer.error("Not a valid number %s for %s, %s", v, type, e.getMessage());
+		} catch (Exception e) {
+			analyzer.error("Cannot convert data %s to type %s", v, type);
+		}
+		return v;
+	}
+}
\ 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..e451eb0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
@@ -0,0 +1,52 @@
+package aQute.bnd.component;
+
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.header.*;
+
+/**
+ * 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 {
+		Parameters header = OSGiHeader.parseHeader(analyzer
+				.getProperty("-dsannotations"));
+		if ( header.size()==0)
+			return false;
+		
+		Instructions instructions = new Instructions(header);
+		Set<Clazz> list = new HashSet<Clazz>(analyzer.getClassspace().values());
+		String sc = analyzer.getProperty(Constants.SERVICE_COMPONENT);
+		List<String> names = new ArrayList<String>();
+		if ( sc != null && sc.trim().length() > 0)
+			names.add(sc);
+		
+		for (Iterator<Clazz> i = list.iterator(); i.hasNext();) {
+			for (Instruction instruction : instructions.keySet()) {
+				Clazz c = i.next();
+
+				if (instruction.matches(c.getFQN())) {
+					if (instruction.isNegated())
+						i.remove();
+					else {
+						ComponentDef definition = AnnotationReader.getDefinition(c, analyzer);
+						if (definition != null) {
+							definition.prepare(analyzer);
+							String name = "OSGI-INF/" + definition.name + ".xml";
+							names.add(name);
+							analyzer.getJar().putResource(name,
+									new TagResource(definition.getTag()));
+						}
+					}
+				}
+			}
+		}
+		sc = Processor.append(names.toArray(new String[names.size()]));
+		analyzer.setProperty(Constants.SERVICE_COMPONENT, sc);
+		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..162bdb4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/ReferenceDef.java
@@ -0,0 +1,95 @@
+package aQute.bnd.component;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+/**
+ * Holds the information in the reference element.
+ */
+
+class ReferenceDef {
+	Version					version = AnnotationReader.V1_1;
+	String					name;
+	String					service;
+	ReferenceCardinality	cardinality;
+	ReferencePolicy			policy;
+	ReferencePolicyOption	policyOption;
+	String					target;
+	String					bind;
+	String					unbind;
+	String					updated;
+
+	/**
+	 * Prepare the reference, will check for any errors.
+	 * 
+	 * @param analyzer the analyzer to report errors to.
+	 * @throws Exception 
+	 */
+	public void prepare(Analyzer analyzer) throws Exception {
+		if (name == null)
+			analyzer.error("No name for a reference");
+		
+		if ((updated != null && !updated.equals("-")) || policyOption!= null)
+			version = max(version, AnnotationReader.V1_2);
+
+		if (target != null) {
+			String error = Verifier.validateFilter(target);
+			if ( error != null)
+				analyzer.error("Invalid target filter %s for %s", target, name);
+		}
+
+		if ( service == null)
+			analyzer.error("No interface specified on %s", name);
+		
+	}
+
+	/**
+	 * Calculate the tag.
+	 * 
+	 * @return a tag for the reference element.
+	 */
+	public Tag getTag() {
+		Tag ref = new Tag("reference");
+		ref.addAttribute("name", name);
+		if (cardinality != null)
+			ref.addAttribute("cardinality", cardinality.toString());
+
+		if (policy != null)
+			ref.addAttribute("policy", policy.toString());
+
+		ref.addAttribute("interface", service);
+
+		if (target != null)
+			ref.addAttribute("target", target);
+
+		if (bind != null && !"-".equals(bind))
+			ref.addAttribute("bind", bind);
+
+		if (unbind != null && !"-".equals(unbind))
+			ref.addAttribute("unbind", unbind);
+
+		if (updated != null && !"-".equals(updated)) 
+			ref.addAttribute("updated", updated);
+
+		if ( policyOption != null)
+			ref.addAttribute("policy-option", policyOption.toString());
+		
+		return ref;
+	}
+
+	static <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/differ/Baseline.java b/bundleplugin/src/main/java/aQute/bnd/differ/Baseline.java
new file mode 100644
index 0000000..eee3e27
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/Baseline.java
@@ -0,0 +1,174 @@
+package aQute.bnd.differ;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.diff.*;
+import aQute.bnd.service.diff.Diff.Ignore;
+import aQute.lib.osgi.*;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+/**
+ * This class maintains
+ * 
+ */
+public class Baseline {
+
+	public static class Info {
+		public String				packageName;
+		public Diff					packageDiff;
+		public Collection<String>	providers;
+		public Map<String, String>	attributes;
+		public Version				newerVersion;
+		public Version				olderVersion;
+		public Version				suggestedVersion;
+		public Version				suggestedIfProviders;
+		public boolean				mismatch;
+		public String				warning="";
+
+	}
+
+	final Differ	differ;
+	final Reporter	bnd;
+
+	public Baseline(Reporter bnd, Differ differ) throws IOException {
+		this.differ = differ;
+		this.bnd = bnd;
+	}
+
+	/**
+	 * This method compares a jar to a baseline jar and returns version
+	 * suggestions if the baseline does not agree with the newer jar. The
+	 * returned set contains all the exported packages.
+	 * 
+	 * @param newer
+	 * @param older
+	 * @return null if ok, otherwise a set of suggested versions for all
+	 *         packages (also the ones that were ok).
+	 * @throws Exception
+	 */
+	public Set<Info> baseline(Jar newer, Jar older, Instructions packageFilters)
+			throws Exception {
+		Tree n = differ.tree(newer);
+		Parameters nExports = getExports(newer);
+		Tree o = differ.tree(older);
+		Parameters oExports = getExports(older);
+		if ( packageFilters == null)
+			packageFilters = new Instructions();
+		
+		return baseline(n, nExports, o, oExports, packageFilters);
+	}
+
+	public Set<Info> baseline(Tree n, Parameters nExports, Tree o,
+			Parameters oExports, Instructions packageFilters)
+			throws Exception {
+		Diff diff = n.diff(o).get("<api>");
+		Set<Info> infos = Create.set();
+
+		for (Diff pdiff : diff.getChildren()) {
+			if (pdiff.getType() != Type.PACKAGE) // Just packages
+				continue;
+
+			if (pdiff.getName().startsWith("java."))
+				continue;
+
+			if (!packageFilters.matches(pdiff.getName()))
+				continue;
+
+			final Info info = new Info();
+			infos.add(info);
+
+			info.packageDiff = pdiff;
+			info.packageName = pdiff.getName();
+			info.attributes = nExports.get(info.packageName);
+			bnd.trace("attrs for %s %s", info.packageName,info.attributes);
+
+			info.newerVersion = getVersion(info.attributes);
+			info.olderVersion = getVersion(oExports.get(info.packageName));
+			if (pdiff.getDelta() == Delta.UNCHANGED) {
+				info.suggestedVersion = info.olderVersion;
+				if( !info.newerVersion.equals(info.olderVersion)) {
+					info.warning += "No difference but versions are equal";
+				}
+			} else if (pdiff.getDelta() == Delta.REMOVED) {
+				info.suggestedVersion = null;
+			} else if (pdiff.getDelta() == Delta.ADDED) {
+				info.suggestedVersion = Version.ONE;
+			} else {
+				// We have an API change
+				info.suggestedVersion = bump(pdiff.getDelta(), info.olderVersion, 1, 0);
+
+				if (info.newerVersion.compareTo(info.suggestedVersion) < 0) {
+					info.mismatch = true; // our suggested version is smaller
+											// than
+											// the
+											// old version!
+
+					// We can fix some major problems by assuming
+					// that an interface is a provider interface
+					if (pdiff.getDelta() == Delta.MAJOR) {
+
+						info.providers = Create.set();
+						if (info.attributes != null)
+							info.providers.addAll(Processor.split(info.attributes
+									.get(Constants.PROVIDER_TYPE_DIRECTIVE)));
+
+						// Calculate the new delta assuming we fix all the major
+						// interfaces
+						// by making them providers
+						Delta tryDelta = pdiff.getDelta(new Ignore() {
+							public boolean contains(Diff diff) {
+								if (diff.getType() == Type.INTERFACE
+										&& diff.getDelta() == Delta.MAJOR) {
+									info.providers.add(Descriptors.getShortName(diff.getName()));
+									return true;
+								}
+								return false;
+							}
+						});
+
+						if (tryDelta != Delta.MAJOR) {
+							info.suggestedIfProviders = bump(tryDelta, info.olderVersion, 1, 0);
+						}
+					}
+				}
+			}
+		}
+		return infos;
+	}
+
+	private Version bump(Delta delta, Version last, int offset, int base) {
+		switch (delta) {
+		case UNCHANGED:
+			return last;
+		case MINOR:
+			return new Version(last.getMajor(), last.getMinor() + offset, base);
+		case MAJOR:
+			return new Version(last.getMajor() + 1, base, base);
+		case ADDED:
+			return last;
+		default:
+			return new Version(last.getMajor(), last.getMinor(), last.getMicro() + offset);
+		}
+	}
+
+	private Version getVersion(Map<String, String> map) {
+		if (map == null)
+			return Version.LOWEST;
+
+		return Version.parseVersion(map.get(Constants.VERSION_ATTRIBUTE));
+	}
+
+	private Parameters getExports(Jar jar) throws Exception {
+		Manifest m = jar.getManifest();
+		if (m == null)
+			return new Parameters();
+
+		return OSGiHeader.parseHeader(m.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/DiffImpl.java b/bundleplugin/src/main/java/aQute/bnd/differ/DiffImpl.java
new file mode 100644
index 0000000..7b675c2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/DiffImpl.java
@@ -0,0 +1,210 @@
+package aQute.bnd.differ;
+
+import static aQute.bnd.service.diff.Delta.*;
+
+import java.util.*;
+
+import aQute.bnd.service.diff.*;
+
+/**
+ * A DiffImpl class compares a newer Element to an older Element. The Element
+ * classes hide all the low level details. A Element class is either either
+ * Structured (has children) or it is a Leaf, it only has a value. The
+ * constructor will first build its children (if any) and then calculate the
+ * delta. Each comparable element is translated to an Element. If necessary the
+ * Element can be sub classed to provide special behavior.
+ */
+
+public class DiffImpl implements Diff, Comparable<DiffImpl> {
+
+	final Element				older;
+	final Element				newer;
+	final Collection<DiffImpl>	children;
+	final Delta					delta;
+
+	/**
+	 * The transitions table defines how the state is escalated depending on the
+	 * children. horizontally is the current delta and this is indexed with the
+	 * child delta for each child. This escalates deltas from below up.
+	 */
+	final static Delta[][]		TRANSITIONS	= {
+			{ IGNORED, UNCHANGED, CHANGED, MICRO, MINOR, MAJOR }, // IGNORED
+			{ IGNORED, UNCHANGED, CHANGED, MICRO, MINOR, MAJOR }, // UNCHANGED
+			{ IGNORED, CHANGED, CHANGED, MICRO, MINOR, MAJOR }, // CHANGED
+			{ IGNORED, MICRO, MICRO, MICRO, MINOR, MAJOR }, // MICRO
+			{ IGNORED, MINOR, MINOR, MINOR, MINOR, MAJOR }, // MINOR
+			{ IGNORED, MAJOR, MAJOR, MAJOR, MAJOR, MAJOR }, // MAJOR
+			{ IGNORED, MAJOR, MAJOR, MAJOR, MAJOR, MAJOR }, // REMOVED
+			{ IGNORED, MINOR, MINOR, MINOR, MINOR, MAJOR }, // ADDED
+											};
+
+	/**
+	 * Compares the newer against the older, traversing the children if
+	 * necessary.
+	 * 
+	 * @param newer
+	 *            The newer Element
+	 * @param older
+	 *            The older Element
+	 * @param types
+	 */
+	DiffImpl(Element newer, Element older) {
+		assert newer != null || older != null;
+		this.older = older;
+		this.newer = newer;
+
+		// Either newer or older can be null, indicating remove or add
+		// so we have to be very careful.
+		Element[] newerChildren = newer == null ? Element.EMPTY : newer.children;
+		Element[] olderChildren = older == null ? Element.EMPTY : older.children;
+
+		int o = 0;
+		int n = 0;
+		List<DiffImpl> children = new ArrayList<DiffImpl>();
+		while (true) {
+			Element nw = n < newerChildren.length ? newerChildren[n] : null;
+			Element ol = o < olderChildren.length ? olderChildren[o] : null;
+			DiffImpl diff;
+
+			if (nw == null && ol == null)
+				break;
+
+			if (nw != null && ol != null) {
+				// we have both sides
+				int result = nw.compareTo(ol);
+				if (result == 0) {
+					// we have two equal named elements
+					// use normal diff
+					diff = new DiffImpl(nw, ol);
+					n++;
+					o++;
+				} else if (result > 0) {
+					// we newer > older, so there is no newer == removed
+					diff = new DiffImpl(null, ol);
+					o++;
+				} else {
+					// we newer < older, so there is no older == added
+					diff = new DiffImpl(nw, null);
+					n++;
+				}
+			} else {
+				// we reached the end of one of the list
+				diff = new DiffImpl(nw, ol);
+				n++;
+				o++;
+			}
+			children.add(diff);
+		}
+
+		// make sure they're read only
+		this.children = Collections.unmodifiableCollection(children);
+		delta = getDelta(null);
+	}
+
+	/**
+	 * Return the absolute delta. Also see
+	 * {@link #getDelta(aQute.bnd.service.diff.Diff.Ignore)} that allows you to
+	 * ignore Diff objects on the fly (and calculate their parents accordingly).
+	 */
+	public Delta getDelta() {
+		return delta;
+	}
+
+	/**
+	 * This getDelta calculates the delta but allows the caller to ignore
+	 * certain Diff objects by calling back the ignore call back parameter. This
+	 * can be useful to ignore warnings/errors.
+	 */
+
+	public Delta getDelta(Ignore ignore) {
+
+		// If ignored, we just return ignore.
+		if (ignore != null && ignore.contains(this))
+			return IGNORED;
+
+		if (newer == null) {
+			return REMOVED;
+		} else if (older == null) {
+			return ADDED;
+		} else {
+			// now we're sure newer and older are both not null
+			assert newer != null && older != null;
+			assert newer.getClass() == older.getClass();
+
+			Delta local = Delta.UNCHANGED;
+
+			for (DiffImpl child : children) {
+				Delta sub = child.getDelta(ignore);
+				if (sub == REMOVED)
+					sub = child.older.remove;
+				else if (sub == ADDED)
+					sub = child.newer.add;
+
+				// The escalate method is used to calculate the default
+				// transition in the
+				// delta based on the children. In general the delta can
+				// only escalate, i.e.
+				// move up in the chain.
+
+				local = TRANSITIONS[sub.ordinal()][local.ordinal()];
+			}
+			return local;
+		}
+	}
+
+	public Type getType() {
+		return (newer == null ? older : newer).getType();
+	}
+
+	public String getName() {
+		return (newer == null ? older : newer).getName();
+	}
+
+	public Collection<? extends Diff> getChildren() {
+		return children;
+	}
+
+	public String toString() {
+		return String.format("%-10s %-10s %s", getDelta(), getType(), getName());
+	}
+
+	public boolean equals(Object other) {
+		if (other instanceof DiffImpl) {
+			DiffImpl o = (DiffImpl) other;
+			return getDelta() == o.getDelta() && getType() == o.getType()
+					&& getName().equals(o.getName());
+		}
+		return false;
+	}
+
+	public int hashCode() {
+		return getDelta().hashCode() ^ getType().hashCode() ^ getName().hashCode();
+	}
+
+	public int compareTo(DiffImpl other) {
+		if (getDelta() == other.getDelta()) {
+			if (getType() == other.getType()) {
+				return getName().compareTo(other.getName());
+			} else
+				return getType().compareTo(other.getType());
+		} else
+			return getDelta().compareTo(other.getDelta());
+	}
+
+	public Diff get(String name) {
+		for (DiffImpl child : children) {
+			if (child.getName().equals(name))
+				return child;
+		}
+		return null;
+	}
+
+	public Tree getOlder() {
+		return older;
+	}
+
+	public Tree getNewer() {
+		return newer;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java b/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java
new file mode 100644
index 0000000..e3a1308
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java
@@ -0,0 +1,181 @@
+package aQute.bnd.differ;
+
+import static aQute.bnd.service.diff.Delta.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.diff.*;
+import aQute.bnd.service.diff.Tree.Data;
+import aQute.lib.hex.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.cryptography.*;
+import aQute.libg.header.*;
+
+
+/**
+ * This Diff Plugin Implementation will compare JARs for their API (based on the
+ * Bundle Class Path and exported packages), the Manifest, and the resources.
+ * The Differences are represented in a {@link Diff} tree.
+ */
+public class DiffPluginImpl implements Differ {
+	
+	/**
+	 * Headers that are considered major enough to parse according to spec and
+	 * compare their constituents
+	 */
+	final static Set<String>				MAJOR_HEADERS	= new TreeSet<String>(
+																	String.CASE_INSENSITIVE_ORDER);
+
+	/**
+	 * Headers that are considered not major enough to be considered
+	 */
+	final static Set<String>				IGNORE_HEADERS	= new TreeSet<String>(
+																	String.CASE_INSENSITIVE_ORDER);
+
+	static {
+		MAJOR_HEADERS.add(Constants.EXPORT_PACKAGE);
+		MAJOR_HEADERS.add(Constants.IMPORT_PACKAGE);
+		MAJOR_HEADERS.add(Constants.REQUIRE_BUNDLE);
+		MAJOR_HEADERS.add(Constants.FRAGMENT_HOST);
+		MAJOR_HEADERS.add(Constants.BUNDLE_SYMBOLICNAME);
+		MAJOR_HEADERS.add(Constants.BUNDLE_LICENSE);
+		MAJOR_HEADERS.add(Constants.BUNDLE_NATIVECODE);
+		MAJOR_HEADERS.add(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
+		MAJOR_HEADERS.add(Constants.DYNAMICIMPORT_PACKAGE);
+
+		IGNORE_HEADERS.add(Constants.TOOL);
+		IGNORE_HEADERS.add(Constants.BND_LASTMODIFIED);
+		IGNORE_HEADERS.add(Constants.CREATED_BY);
+	}
+
+	/**
+	 * 
+	 * @see aQute.bnd.service.diff.Differ#diff(aQute.lib.resource.Jar,
+	 *      aQute.lib.resource.Jar)
+	 */
+	public Tree tree(File newer) throws Exception {
+		Jar jnewer = new Jar(newer);
+		try {
+			return tree(jnewer);
+		} finally {
+			jnewer.close();
+		}
+	}
+
+	/**
+	 * 
+	 * @see aQute.bnd.service.diff.Differ#diff(aQute.lib.resource.Jar,
+	 *      aQute.lib.resource.Jar)
+	 */
+	public Tree tree(Jar newer) throws Exception {
+		Analyzer anewer = new Analyzer();
+		try {
+				anewer.setJar(newer);
+				return tree(anewer);
+		} finally {
+			anewer.setJar((Jar) null);
+			anewer.close();
+		}
+	}
+
+	public Tree tree(Analyzer newer) throws Exception {
+		return bundleElement(newer);
+	}
+
+	/**
+	 * Create an element representing a bundle from the Jar.
+	 * 
+	 * @param infos 
+	 * @param jar
+	 *            The Jar to be analyzed
+	 * @return the elements that should be compared
+	 * @throws Exception
+	 */
+	private Element bundleElement(Analyzer analyzer) throws Exception {
+		List<Element> result = new ArrayList<Element>();
+
+		Manifest manifest = analyzer.getJar().getManifest();
+
+		if (manifest != null) {
+			result.add(JavaElement.getAPI(analyzer));
+			result.add(manifestElement(manifest));
+		}
+		result.add(resourcesElement(analyzer.getJar()));
+		return new Element(Type.BUNDLE, analyzer.getJar().getName(), result, CHANGED, CHANGED,
+				null);
+	}
+
+	/**
+	 * Create an element representing all resources in the JAR
+	 * 
+	 * @param jar
+	 * @return
+	 * @throws Exception
+	 */
+	private Element resourcesElement(Jar jar) throws Exception {
+		List<Element> resources = new ArrayList<Element>();
+		for (Map.Entry<String, Resource> entry : jar.getResources().entrySet()) {
+
+			InputStream in = entry.getValue().openInputStream();
+			try {
+				Digester<SHA1> digester = SHA1.getDigester();
+				IO.copy(in, digester);
+				String value = Hex.toHexString(digester.digest().digest());
+				resources
+						.add(new Element(Type.RESOURCE, entry.getKey()+"="+value, null, CHANGED, CHANGED, null));
+			} finally {
+				in.close();
+			}
+		}
+		return new Element(Type.RESOURCES, "<resources>", resources, CHANGED, CHANGED, null);
+	}
+
+
+
+
+	/**
+	 * Create an element for each manifest header. There are
+	 * {@link #IGNORE_HEADERS} and {@link #MAJOR_HEADERS} that will be treated
+	 * differently.
+	 * 
+	 * @param manifest
+	 * @return
+	 */
+
+	private Element manifestElement(Manifest manifest) {
+		List<Element> result = new ArrayList<Element>();
+
+		for (Object key : manifest.getMainAttributes().keySet()) {
+			String header = key.toString();
+			String value = manifest.getMainAttributes().getValue(header);
+			if (IGNORE_HEADERS.contains(header))
+				continue;
+
+			if (MAJOR_HEADERS.contains(header)) {
+				Parameters clauses = OSGiHeader.parseHeader(value);
+				Collection<Element> clausesDef = new ArrayList<Element>();
+				for (Map.Entry<String, Attrs> clause : clauses.entrySet()) {
+					Collection<Element> parameterDef = new ArrayList<Element>();
+					for (Map.Entry<String, String> parameter : clause.getValue().entrySet()) {
+						parameterDef.add(new Element(Type.PARAMETER, parameter.getKey() + ":" + parameter
+								.getValue(), null, CHANGED, CHANGED, null));
+					}
+					clausesDef.add(new Element(Type.CLAUSE, clause.getKey(), parameterDef,
+							CHANGED, CHANGED, null));
+				}
+				result.add(new Element(Type.HEADER, header, clausesDef, CHANGED, CHANGED, null));
+			} else {
+				result.add(new Element(Type.HEADER, header +":"+ value, null,CHANGED, CHANGED, null));
+			}
+		}
+		return new Element(Type.MANIFEST, "<manifest>", result, CHANGED, CHANGED, null);
+	}
+
+	public Tree deserialize(Data data) throws Exception {
+		return new Element(data);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/Element.java b/bundleplugin/src/main/java/aQute/bnd/differ/Element.java
new file mode 100644
index 0000000..602f95c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/Element.java
@@ -0,0 +1,144 @@
+package aQute.bnd.differ;
+
+import java.util.*;
+
+import aQute.bnd.service.diff.*;
+
+/**
+ * An element can be compared to another element of the same type. Elements with
+ * the same name and same place in the hierarchy should have the same type. The
+ * idea is that for a certain resource type you create an element (Structured or
+ * Leaf). This process is done for the newer and older resource.
+ * <p>
+ * A Leaf type has a value, comparison is rather simple in this case.
+ * <p>
+ * A Structured type has named children. The comparison between the newer and
+ * older child elements is then done on their name. Two elements with the same
+ * name are then matched.
+ * <p>
+ * The classes are prepared for extension but so far it turned out to be
+ * unnecessary.
+ */
+
+class Element implements Comparable<Element>, Tree {
+	final static Element[]	EMPTY	= new Element[0];
+	final Type				type;
+	final String			name;
+	final Delta				add;
+	final Delta				remove;
+	final String			comment;
+	final Element[]			children;
+
+	Element(Type type, String name) {
+		this(type, name, null, Delta.MINOR, Delta.MAJOR, null);
+	}
+
+	Element(Type type, String name, Element... children) {
+		this(type, name, Arrays.asList(children), Delta.MINOR, Delta.MAJOR, null);
+	}
+
+	Element(Type type, String name, Collection<? extends Element> children, Delta add,
+			Delta remove, String comment) {
+		this.type = type;
+		this.name = name;
+		this.add = add;
+		this.remove = remove;
+		this.comment = comment;
+		if (children != null && children.size() > 0) {
+			this.children = children.toArray(new Element[children.size()]);
+			Arrays.sort(this.children);
+		} else
+			this.children = EMPTY;
+	}
+
+	public Element(Data data) {
+		this.name = data.name;
+		this.type = data.type;
+		this.comment = data.comment;
+		this.add = data.add;
+		this.remove = data.rem;
+		if (data.children == null)
+			children = EMPTY;
+		else {
+			this.children = new Element[data.children.length];
+			for (int i = 0; i < children.length; i++)
+				children[i] = new Element(data.children[i]);
+			Arrays.sort(this.children);
+		}
+	}
+	public Data serialize() {
+		Data data = new Data();
+		data.type = this.type;
+		data.name = this.name;
+		data.add = this.add;
+		data.rem = this.remove;
+		data.comment = this.comment;
+		if (children.length != 0) {
+			data.children = new Data[children.length];
+			for (int i = 0; i < children.length; i++) {
+				data.children[i] = children[i].serialize();
+			}
+		}
+		return data;
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	String getComment() {
+		return comment;
+	}
+
+	public int compareTo(Element other) {
+		if (type == other.type)
+			return name.compareTo(other.name);
+		else
+			return type.compareTo(other.type);
+	}
+
+	public boolean equals(Object other) {
+		if (other == null || getClass() != other.getClass())
+			return false;
+
+		return compareTo((Element) other) == 0;
+	}
+
+	public int hashCode() {
+		return type.hashCode() ^ name.hashCode();
+	}
+
+	public Tree[] getChildren() {
+		return children;
+	}
+
+	public Delta ifAdded() {
+		return add;
+	}
+
+	public Delta ifRemoved() {
+		return remove;
+	}
+
+	public Diff diff(Tree older) {
+		return new DiffImpl(this, (Element) older);
+	}
+
+	public Element get(String name) {
+		for (Element e : children) {
+			if (e.name.equals(name))
+				return e;
+		}
+		return null;
+	}
+	
+	public String toString() {
+		return type + " " + name + " (" + add + "/" + remove + ")";
+	}
+
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/JavaElement.java b/bundleplugin/src/main/java/aQute/bnd/differ/JavaElement.java
new file mode 100644
index 0000000..e9a77a5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/JavaElement.java
@@ -0,0 +1,655 @@
+package aQute.bnd.differ;
+
+import static aQute.bnd.service.diff.Delta.*;
+import static aQute.bnd.service.diff.Type.*;
+import static java.lang.reflect.Modifier.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.atomic.*;
+import java.util.jar.*;
+
+import aQute.bnd.annotation.*;
+import aQute.bnd.service.diff.*;
+import aQute.bnd.service.diff.Type;
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.JAVA;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.version.Version;
+
+/**
+ * An element that compares the access field in a binary compatible way. This
+ * element is used for classes, methods, constructors, and fields. For that
+ * reason we also included the only method that uses this class as a static
+ * method.
+ * <p>
+ * Packages
+ * <ul>
+ * <li>MAJOR - Remove a public type
+ * <li>MINOR - Add a public class
+ * <li>MINOR - Add an interface
+ * <li>MINOR - Add a method to a class
+ * <li>MINOR - Add a method to a provider interface
+ * <li>MAJOR - Add a method to a consumer interface
+ * <li>MINOR - Add a field
+ * <li>MICRO - Add an annotation to a member
+ * <li>MINOR - Change the value of a constant
+ * <li>MICRO - -abstract
+ * <li>MICRO - -final
+ * <li>MICRO - -protected
+ * <li>MAJOR - +abstract
+ * <li>MAJOR - +final
+ * <li>MAJOR - +protected
+ * </ul>
+ * 
+ */
+
+class JavaElement {
+	final static EnumSet<Type>			INHERITED		= EnumSet.of(FIELD, METHOD, EXTENDS,
+																IMPLEMENTS);
+	private static final Element		PROTECTED		= new Element(ACCESS, "protected", null,
+																MAJOR, MINOR, null);
+	private static final Element		STATIC			= new Element(ACCESS, "static", null,
+																MAJOR, MAJOR, null);
+	private static final Element		ABSTRACT		= new Element(ACCESS, "abstract", null,
+																MAJOR, MINOR, null);
+	private static final Element		FINAL			= new Element(ACCESS, "final", null, MAJOR,
+																MINOR, null);
+	// private static final Element DEPRECATED = new Element(ACCESS,
+	// "deprecated", null,
+	// CHANGED, CHANGED, null);
+
+	final Analyzer						analyzer;
+	final Map<PackageRef, Instructions>	providerMatcher	= Create.map();
+	final Set<TypeRef>					notAccessible	= Create.set();
+	final Map<Object, Element>			cache			= Create.map();
+	MultiMap<PackageRef, //
+	Element>							packages;
+	final MultiMap<TypeRef, //
+	Element>							covariant		= new MultiMap<TypeRef, Element>();
+	final Set<JAVA>						javas			= Create.set();
+	final Packages						exports;
+
+	/**
+	 * Create an element for the API. We take the exported packages and traverse
+	 * those for their classes. If there is no manifest or it does not describe
+	 * a bundle we assume the whole contents is exported.
+	 * 
+	 * @param infos
+	 */
+	JavaElement(Analyzer analyzer) throws Exception {
+		this.analyzer = analyzer;
+
+		Manifest manifest = analyzer.getJar().getManifest();
+		if (manifest != null
+				&& manifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION) != null) {
+			exports = new Packages();
+			for (Map.Entry<String, Attrs> entry : OSGiHeader.parseHeader(
+					manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE)).entrySet())
+				exports.put(analyzer.getPackageRef(entry.getKey()), entry.getValue());
+		} else
+			exports = analyzer.getContained();
+		//
+		// We have to gather the -providers and parse them into instructions
+		// so we can efficiently match them during class parsing to find
+		// out who the providers and consumers are
+		//
+
+		for (Entry<PackageRef, Attrs> entry : exports.entrySet()) {
+			String value = entry.getValue().get(Constants.PROVIDER_TYPE_DIRECTIVE);
+			if (value != null) {
+				providerMatcher.put(entry.getKey(), new Instructions(value));
+			}
+		}
+
+		// we now need to gather all the packages but without
+		// creating the packages yet because we do not yet know
+		// which classes are accessible
+
+		packages = new MultiMap<PackageRef, Element>();
+
+		for (Clazz c : analyzer.getClassspace().values()) {
+			if (c.isPublic() || c.isProtected()) {
+				PackageRef packageName = c.getClassName().getPackageRef();
+
+				if (exports.containsKey(packageName)) {
+					Element cdef = classElement(c);
+					packages.add(packageName, cdef);
+				}
+			}
+		}
+
+	}
+
+	static Element getAPI(Analyzer analyzer) throws Exception {
+		analyzer.analyze();
+		JavaElement te = new JavaElement(analyzer);
+		return te.getLocalAPI();
+	}
+
+	private Element getLocalAPI() throws Exception {
+		List<Element> result = new ArrayList<Element>();
+
+		for (Map.Entry<PackageRef, List<Element>> entry : packages.entrySet()) {
+			List<Element> set = entry.getValue();
+			for (Iterator<Element> i = set.iterator(); i.hasNext();) {
+				
+				if (notAccessible.contains( analyzer.getTypeRefFromFQN(i.next().getName())))
+					i.remove();
+				
+			}
+			String version = exports.get(entry.getKey()).get(Constants.VERSION_ATTRIBUTE);
+			if (version != null) {
+				Version v = new Version(version);
+				set.add(new Element(Type.VERSION, v.getWithoutQualifier().toString(), null,
+						IGNORED, IGNORED, null));
+			}
+			Element pd = new Element(Type.PACKAGE, entry.getKey().getFQN(), set, MINOR, MAJOR, null);
+			result.add(pd);
+		}
+
+		for (JAVA java : javas) {
+			result.add(new Element(CLASS_VERSION, java.toString(), null, Delta.CHANGED,
+					Delta.CHANGED, null));
+		}
+
+		return new Element(Type.API, "<api>", result, CHANGED, CHANGED, null);
+	}
+
+	/**
+	 * Calculate the class element. This requires parsing the class file and
+	 * finding all the methods that were added etc. The parsing will take super
+	 * interfaces and super classes into account. For this reason it maintains a
+	 * queue of classes/interfaces to parse.
+	 * 
+	 * @param analyzer
+	 * @param clazz
+	 * @param infos
+	 * @return
+	 * @throws Exception
+	 */
+	Element classElement(final Clazz clazz) throws Exception {
+		Element e = cache.get(clazz);
+		if (e != null)
+			return e;
+
+		final StringBuilder comment = new StringBuilder();
+		final Set<Element> members = new HashSet<Element>();
+		final Set<MethodDef> methods = Create.set();
+		final Set<Clazz.FieldDef> fields = Create.set();
+		final MultiMap<Clazz.Def, Element> annotations = new MultiMap<Clazz.Def, Element>();
+
+		final TypeRef name = clazz.getClassName();
+
+		final String fqn = name.getFQN();
+		final String shortName = name.getShortName();
+
+		// Check if this clazz is actually a provider or not
+		// providers must be listed in the exported package in the
+		// PROVIDER_TYPE directive.
+		Instructions matchers = providerMatcher.get(name.getPackageRef());
+		boolean p = matchers != null && matchers.matches(shortName);
+		final AtomicBoolean provider = new AtomicBoolean(p);
+
+		//
+		// Check if we already had this clazz in the cache
+		//
+
+		Element before = cache.get(clazz); // for super classes
+		if (before != null)
+			return before;
+
+		clazz.parseClassFileWithCollector(new ClassDataCollector() {
+			boolean			memberEnd;
+			Clazz.FieldDef	last;
+
+			@Override public void version(int minor, int major) {
+				javas.add(Clazz.JAVA.getJava(major, minor));
+			}
+
+			@Override public void method(MethodDef defined) {
+				if ((defined.isProtected() || defined.isPublic())) {
+					last = defined;
+					methods.add(defined);
+				} else {
+					last = null;
+				}
+			}
+
+			@Override public void deprecated() {
+				if (memberEnd)
+					clazz.setDeprecated(true);
+				else
+					last.setDeprecated(true);
+			}
+
+			@Override public void field(Clazz.FieldDef defined) {
+				if (defined.isProtected() || defined.isPublic()) {
+					last = defined;
+					fields.add(defined);
+				} else
+					last = null;
+			}
+
+			@Override public void constant(Object o) {
+				if (last != null) {
+					// Must be accessible now
+					last.setConstant(o);
+				}
+			}
+
+			@Override public void extendsClass(TypeRef name) throws Exception {
+				String comment = null;
+				if (!clazz.isInterface())
+					comment = inherit(members, name);
+
+				Clazz c = analyzer.findClass(name);
+				if ((c == null || c.isPublic()) && !name.isObject())
+					members.add(new Element(Type.EXTENDS, name.getFQN(), null, MICRO, MAJOR,
+							comment));
+			}
+
+			@Override public void implementsInterfaces(TypeRef names[]) throws Exception {
+				// TODO is interface reordering important for binary
+				// compatibility??
+
+				for (TypeRef name : names) {
+
+					String comment = null;
+					if (clazz.isInterface() || clazz.isAbstract())
+						comment = inherit(members, name);
+					members.add(new Element(Type.IMPLEMENTS, name.getFQN(), null, MINOR, MAJOR,
+							comment));
+				}
+			}
+
+			/**
+			 * @param members
+			 * @param name
+			 * @param comment
+			 * @return
+			 */
+			Set<Element>	OBJECT	= Create.set();
+
+			public String inherit(final Set<Element> members, TypeRef name) throws Exception {
+				if (name.isObject()) {
+					if (OBJECT.isEmpty()) {
+						Clazz c = analyzer.findClass(name);
+						Element s = classElement(c);
+						for (Element child : s.children) {
+							if (INHERITED.contains(child.type)) {
+								String n = child.getName();
+								if (child.type == METHOD) {
+									if (n.startsWith("<init>")
+											|| "getClass()".equals(child.getName())
+											|| n.startsWith("wait(") || n.startsWith("notify(")
+											|| n.startsWith("notifyAll("))
+										continue;
+								}
+								OBJECT.add(child);
+							}
+						}
+					}
+					members.addAll(OBJECT);
+				} else {
+
+					Clazz c = analyzer.findClass(name);
+					if (c == null) {
+						return "Cannot load " + name;
+					} else {
+						Element s = classElement(c);
+						for (Element child : s.children) {
+							if (INHERITED.contains(child.type) && !child.name.startsWith("<")) {
+								members.add(child);
+							}
+						}
+					}
+				}
+				return null;
+			}
+
+			@Override public void annotation(Annotation annotation) {
+				Collection<Element> properties = Create.set();
+				if (Deprecated.class.getName().equals(annotation.getName().getFQN())) {
+					if (memberEnd)
+						clazz.setDeprecated(true);
+					else
+						last.setDeprecated(true);
+					return;
+				}
+
+				for (String key : annotation.keySet()) {
+					StringBuilder sb = new StringBuilder();
+					sb.append(key);
+					sb.append('=');
+					toString(sb, annotation.get(key));
+
+					properties.add(new Element(Type.PROPERTY, sb.toString(), null, CHANGED,
+							CHANGED, null));
+				}
+
+				if (memberEnd) {
+					members.add(new Element(Type.ANNOTATED, annotation.getName().getFQN(),
+							properties, CHANGED, CHANGED, null));
+					if (ProviderType.class.getName().equals(annotation.getName().getFQN())) {
+						provider.set(true);
+					} else if (ConsumerType.class.getName().equals(annotation.getName().getFQN())) {
+						provider.set(false);
+					}
+				} else if (last != null)
+					annotations.add(last, new Element(Type.ANNOTATED,
+							annotation.getName().getFQN(), properties, CHANGED, CHANGED, null));
+			}
+
+			private void toString(StringBuilder sb, Object object) {
+
+				if (object.getClass().isArray()) {
+					sb.append('[');
+					int l = Array.getLength(object);
+					for (int i = 0; i < l; i++)
+						toString(sb, Array.get(object, i));
+					sb.append(']');
+				} else
+					sb.append(object);
+			}
+
+			@Override public void innerClass(TypeRef innerClass, TypeRef outerClass,
+					String innerName, int innerClassAccessFlags) throws Exception {
+				Clazz clazz = analyzer.findClass(innerClass);
+				if (clazz != null)
+					clazz.setInnerAccess(innerClassAccessFlags);
+
+				if (Modifier.isProtected(innerClassAccessFlags)
+						|| Modifier.isPublic(innerClassAccessFlags))
+					return;
+				notAccessible.add(innerClass);
+			}
+
+			@Override public void memberEnd() {
+				memberEnd = true;
+			}
+		});
+
+		// This is the heart of the semantic versioning. If we
+		// add or remove a method from an interface then
+		Delta add;
+		Delta remove;
+		Type type;
+
+		// Calculate the type of the clazz. A class
+		// can be an interface, class, enum, or annotation
+
+		if (clazz.isInterface())
+			if (clazz.isAnnotation())
+				type = Type.INTERFACE;
+			else
+				type = Type.ANNOTATION;
+		else if (clazz.isEnum())
+			type = Type.ENUM;
+		else
+			type = Type.CLASS;
+
+		if (type == Type.INTERFACE) {
+			if (provider.get()) {
+				// Adding a method for a provider is not an issue
+				// because it must be aware of the changes
+				add = MINOR;
+
+				// Removing a method influences consumers since they
+				// tend to call this guy.
+				remove = MAJOR;
+			} else {
+				// Adding a method is a major change
+				// because the consumer has to implement it
+				// or the provider will call a non existent
+				// method on the consumer
+				add = MAJOR;
+
+				// Removing a method is not an issue because the
+				// provider, which calls this contract must be
+				// aware of the removal
+
+				remove = MINOR;
+			}
+		} else {
+			// Adding a method to a class can never do any harm
+			add = MINOR;
+
+			// Removing it will likely hurt consumers
+			remove = MAJOR;
+		}
+
+		// Remove all synthetic methods, we need
+		// to treat them special for the covariant returns
+
+		Set<MethodDef> synthetic = Create.set();
+		for (Iterator<MethodDef> i = methods.iterator(); i.hasNext();) {
+			MethodDef m = i.next();
+			if (m.isSynthetic()) {
+				synthetic.add(m);
+				i.remove();
+			}
+		}
+
+		for (MethodDef m : methods) {
+			List<Element> children = annotations.get(m);
+			if (children == null)
+				children = new ArrayList<Element>();
+
+			access(children, m.getAccess(), m.isDeprecated());
+
+			// A final class cannot be extended, ergo,
+			// all methods defined in it are by definition
+			// final. However, marking them final (either
+			// on the method or inheriting it from the class)
+			// will create superfluous changes if we
+			// override a method from a super class that was not
+			// final. So we actually remove the final for methods
+			// in a final class.
+			if (clazz.isFinal())
+				children.remove(FINAL);
+
+			// for covariant types we need to add the return types
+			// and all the implemented and extended types. This is already
+			// do for us when we get the element of the return type.
+
+			getCovariantReturns(children, m.getType());
+
+			for (Iterator<MethodDef> i = synthetic.iterator(); i.hasNext();) {
+				MethodDef s = i.next();
+				if (s.getName().equals(m.getName())
+						&& Arrays.equals(s.getPrototype(), m.getPrototype())) {
+					i.remove();
+					getCovariantReturns(children, s.getType());
+				}
+			}
+
+			Element member = new Element(Type.METHOD, m.getName() + toString(m.getPrototype()),
+					children, add, remove, null);
+
+			if (!members.add(member)) {
+				members.remove(member);
+				members.add(member);
+			}
+		}
+
+		/**
+		 * Repeat for the remaining synthetic methods
+		 */
+		for (MethodDef m : synthetic) {
+			List<Element> children = annotations.get(m);
+			if (children == null)
+				children = new ArrayList<Element>();
+			access(children, m.getAccess(), m.isDeprecated());
+
+			// A final class cannot be extended, ergo,
+			// all methods defined in it are by definition
+			// final. However, marking them final (either
+			// on the method or inheriting it from the class)
+			// will create superfluous changes if we
+			// override a method from a super class that was not
+			// final. So we actually remove the final for methods
+			// in a final class.
+			if (clazz.isFinal())
+				children.remove(FINAL);
+
+			// for covariant types we need to add the return types
+			// and all the implemented and extended types. This is already
+			// do for us when we get the element of the return type.
+
+			getCovariantReturns(children, m.getType());
+
+			Element member = new Element(Type.METHOD, m.getName() + toString(m.getPrototype()),
+					children, add, remove, "synthetic");
+
+			if (!members.add(member)) {
+				members.remove(member);
+				members.add(member);
+			}
+		}
+
+		for (Clazz.FieldDef f : fields) {
+			List<Element> children = annotations.get(f);
+			if (children == null)
+				children = new ArrayList<Element>();
+
+			// Fields can have a constant value, this is a new element
+			if (f.getConstant() != null) {
+				children.add(new Element(Type.CONSTANT, f.getConstant().toString(), null, CHANGED,
+						CHANGED, null));
+			}
+
+			access(children, f.getAccess(), f.isDeprecated());
+			Element member = new Element(Type.FIELD, f.getType().getFQN() + " " + f.getName(),
+					children, MINOR, MAJOR, null);
+
+			if (!members.add(member)) {
+				members.remove(member);
+				members.add(member);
+			}
+		}
+
+		access(members, clazz.getAccess(), clazz.isDeprecated());
+
+		// And make the result
+		Element s = new Element(type, fqn, members, MINOR, MAJOR, comment.length() == 0 ? null
+				: comment.toString());
+		cache.put(clazz, s);
+		return s;
+	}
+
+	private String toString(TypeRef[] prototype) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("(");
+		String del = "";
+		for (TypeRef ref : prototype) {
+			sb.append(del);
+			sb.append(ref.getFQN());
+			del = ",";
+		}
+		sb.append(")");
+		return sb.toString();
+	}
+
+	static Element	BOOLEAN_R	= new Element(RETURN, "boolean");
+	static Element	BYTE_R		= new Element(RETURN, "byte");
+	static Element	SHORT_R		= new Element(RETURN, "short");
+	static Element	CHAR_R		= new Element(RETURN, "char");
+	static Element	INT_R		= new Element(RETURN, "int");
+	static Element	LONG_R		= new Element(RETURN, "long");
+	static Element	FLOAT_R		= new Element(RETURN, "float");
+	static Element	DOUBLE_R	= new Element(RETURN, "double");
+
+	private void getCovariantReturns(Collection<Element> elements, TypeRef type) throws Exception {
+		if (type == null || type.isObject())
+			return;
+
+		if (type.isPrimitive()) {
+			if (type.getFQN().equals("void"))
+				return;
+
+			String name = type.getBinary();
+			Element e;
+			switch (name.charAt(0)) {
+			case 'Z':
+				e = BOOLEAN_R;
+				break;
+			case 'S':
+				e = SHORT_R;
+				break;
+			case 'I':
+				e = INT_R;
+				break;
+			case 'B':
+				e = BYTE_R;
+				break;
+			case 'C':
+				e = CHAR_R;
+				break;
+			case 'J':
+				e = LONG_R;
+				break;
+			case 'F':
+				e = FLOAT_R;
+				break;
+			case 'D':
+				e = DOUBLE_R;
+				break;
+
+			default:
+				throw new IllegalArgumentException("Unknown primitive " + type);
+			}
+			elements.add(e);
+			return;
+		}
+
+		List<Element> set = covariant.get(type);
+		if (set != null) {
+			elements.addAll(set);
+			return;
+		}
+
+		Element current = new Element(RETURN, type.getFQN());
+		Clazz clazz = analyzer.findClass(type);
+		if (clazz == null) {
+			elements.add(current);
+			return;
+		}
+
+		set = Create.list();
+		set.add(current);
+		getCovariantReturns(set, clazz.getSuper());
+
+		TypeRef[] interfaces = clazz.getInterfaces();
+		if (interfaces != null)
+			for (TypeRef intf : interfaces) {
+				getCovariantReturns(set, intf);
+			}
+
+		covariant.put(type, set);
+		elements.addAll(set);
+	}
+
+	private static void access(Collection<Element> children, int access, boolean deprecated) {
+		if (!isPublic(access))
+			children.add(PROTECTED);
+		if (isAbstract(access))
+			children.add(ABSTRACT);
+		if (isFinal(access))
+			children.add(FINAL);
+		if (isStatic(access))
+			children.add(STATIC);
+
+		// Ignore for now
+		// if (deprecated)
+		// children.add(DEPRECATED);
+
+	}
+
+}
\ No newline at end of file
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/changed.txt b/bundleplugin/src/main/java/aQute/bnd/help/changed.txt
new file mode 100644
index 0000000..14d0bd2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/help/changed.txt
@@ -0,0 +1,9 @@
+0.0.325
+  - No longer flattening properties starting with - because for version policy, the 
+    context macro ${@} is not valid. I think this is true for more things. So they are now
+    unexpanded.
+  - toclasspath can now take a suffix parameter, possibly empty
+  - Include-Resource can now take optional parameters:
+      flatten:= (true|false). Default is false. Create recursive directories in the output or not.
+      recursive:= (true|false) Default is true. Will descend any directories or not
+      
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/help/syntax.properties b/bundleplugin/src/main/java/aQute/bnd/help/syntax.properties
new file mode 100644
index 0000000..14ec642
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/help/syntax.properties
@@ -0,0 +1,342 @@
+Add=Holders of DmtPermission with the Add action present can create new nodes in the DMT, that is they are authorized to execute the createInteriorNode() and createLeafNode() methods of the DmtSession.
+Delete=Holders of DmtPermission with the Delete action present can delete nodes from the DMT, that is they are authorized to execute the deleteNode() method of the DmtSession.
+Exec=Holders of DmtPermission with the Exec action present can execute nodes in the DMT, that is they are authorized to call the execute() method of the DmtSession.
+Get=Holders of DmtPermission with the Get action present can query DMT node value or properties, that is they are authorized to execute the isLeafNode(), getNodeAcl(), getEffectiveNodeAcl(), getMetaNode(), getNodeValue(), getChildNodeNames(), getNodeTitle(), getNodeVersion(), getNodeTimeStamp(), getNodeSize() and getNodeType() methods of the DmtSession.
+Replace=Holders of DmtPermission with the Replace action present can update DMT node value or properties, that is they are authorized to execute the setNodeAcl(), setNodeTitle(), setNodeValue(), setNodeType() and renameNode() methods of the DmtSession.
+get=The action string get.
+register=The action string register.
+export=The action string export.
+exportonly=The action string exportonly.
+import=The action string import.
+System Bundle=Location identifier of the OSGi system bundle , which is defined to be "System Bundle".
+system.bundle=Alias for the symbolic name of the OSGi system bundle .
+Bundle-Category=Manifest header identifying the bundle's category.
+Bundle-ClassPath=Manifest header identifying a list of directories and embedded JAR files, which are bundle resources used to extend the bundle's classpath.
+Bundle-Copyright=Manifest header identifying the bundle's copyright information.
+Bundle-Description=Manifest header containing a brief description of the bundle's functionality.
+Bundle-Name=Manifest header identifying the bundle's name.
+Bundle-NativeCode=Manifest header identifying a number of hardware environments and the native language code libraries that the bundle is carrying for each of these environments.
+Export-Package=Manifest header identifying the packages that the bundle offers to the Framework for export.
+Export-Service=Manifest header identifying the fully qualified class names of the services that the bundle may register (used for informational purposes only).
+Import-Package=Manifest header identifying the packages on which the bundle depends.
+DynamicImport-Package=Manifest header identifying the packages that the bundle may dynamically import during execution.
+Import-Service=Manifest header identifying the fully qualified class names of the services that the bundle requires (used for informational purposes only).
+Bundle-Vendor=Manifest header identifying the bundle's vendor.
+Bundle-Version=Manifest header identifying the bundle's version.
+Bundle-DocURL=Manifest header identifying the bundle's documentation URL, from which further information about the bundle may be obtained.
+Bundle-ContactAddress=Manifest header identifying the contact address where problems with the bundle may be reported; for example, an email address.
+Bundle-Activator=Manifest header attribute identifying the bundle's activator class.
+Bundle-UpdateLocation=Manifest header identifying the location from which a new bundle version is obtained during a bundle update operation.
+specification-version=Manifest header attribute identifying the version of a package specified in the Export-Package or Import-Package manifest header.
+processor=Manifest header attribute identifying the processor required to run native bundle code specified in the Bundle-NativeCode manifest header).
+osname=Manifest header attribute identifying the operating system required to run native bundle code specified in the Bundle-NativeCode manifest header).
+osversion=Manifest header attribute identifying the operating system version required to run native bundle code specified in the Bundle-NativeCode manifest header).
+language=Manifest header attribute identifying the language in which the native bundle code is written specified in the Bundle-NativeCode manifest header.
+Bundle-RequiredExecutionEnvironment=Manifest header identifying the required execution environment for the bundle.
+Bundle-SymbolicName=Manifest header identifying the bundle's symbolic name.
+singleton=Manifest header directive identifying whether a bundle is a singleton.
+fragment-attachment=Manifest header directive identifying if and when a fragment may attach to a host bundle.
+always=Manifest header directive value identifying a fragment attachment type of always.
+resolve-time=Manifest header directive value identifying a fragment attachment type of resolve-time.
+never=Manifest header directive value identifying a fragment attachment type of never.
+Bundle-Localization=Manifest header identifying the base name of the bundle's localization entries.
+OSGI-INF/l10n/bundle=Default value for the Bundle-Localization manifest header.
+Require-Bundle=Manifest header identifying the symbolic names of other bundles required by the bundle.
+bundle-version=Manifest header attribute identifying a range of versions for a bundle specified in the Require-Bundle or Fragment-Host manifest headers.
+Fragment-Host=Manifest header identifying the symbolic name of another bundle for which that the bundle is a fragment.
+selection-filter=Manifest header attribute is used for selection by filtering based upon system properties.
+Bundle-ManifestVersion=Manifest header identifying the bundle manifest version.
+version=Manifest header attribute identifying the version of a package specified in the Export-Package or Import-Package manifest header.
+bundle-symbolic-name=Manifest header attribute identifying the symbolic name of a bundle that exports a package specified in the Import-Package manifest header.
+resolution=Manifest header directive identifying the resolution type in the Import-Package or Require-Bundle manifest header.
+mandatory=Manifest header directive value identifying a mandatory resolution type.
+optional=Manifest header directive value identifying an optional resolution type.
+uses=Manifest header directive identifying a list of packages that an exported package uses.
+include=Manifest header directive identifying a list of classes to include in the exported package.
+exclude=Manifest header directive identifying a list of classes to exclude in the exported package..
+mandatory=Manifest header directive identifying names of matching attributes which must be specified by matching Import-Package statements in the Export-Package manifest header.
+visibility=Manifest header directive identifying the visibility of a required bundle in the Require-Bundle manifest header.
+private=Manifest header directive value identifying a private visibility type.
+reexport=Manifest header directive value identifying a reexport visibility type.
+extension=Manifest header directive identifying the type of the extension fragment.
+framework=Manifest header directive value identifying the type of extension fragment.
+bootclasspath=Manifest header directive value identifying the type of extension fragment.
+Bundle-ActivationPolicy=Manifest header identifying the bundle's activation policy.
+lazy=Bundle activation policy declaring the bundle must be activated when the first class load is made from the bundle.
+org.osgi.framework.version=Framework environment property identifying the Framework version.
+org.osgi.framework.vendor=Framework environment property identifying the Framework implementation vendor.
+org.osgi.framework.language=Framework environment property identifying the Framework implementation language (see ISO 639 for possible values).
+org.osgi.framework.os.name=Framework environment property identifying the Framework host-computer's operating system.
+org.osgi.framework.os.version=Framework environment property identifying the Framework host-computer's operating system version number.
+org.osgi.framework.processor=Framework environment property identifying the Framework host-computer's processor name.
+org.osgi.framework.executionenvironment=Framework environment property identifying execution environments provided by the Framework.
+org.osgi.framework.bootdelegation=Framework environment property identifying packages for which the Framework must delegate class loading to the parent class loader of the bundle.
+org.osgi.framework.system.packages=Framework environment property identifying packages which the system bundle must export.
+org.osgi.framework.system.packages.extra=Framework environment property identifying extra packages which the system bundle must export from the current execution environment.
+org.osgi.supports.framework.extension=Framework environment property identifying whether the Framework supports framework extension bundles.
+org.osgi.supports.bootclasspath.extension=Framework environment property identifying whether the Framework supports bootclasspath extension bundles.
+org.osgi.supports.framework.fragment=Framework environment property identifying whether the Framework supports fragment bundles.
+org.osgi.supports.framework.requirebundle=Framework environment property identifying whether the Framework supports the Require-Bundle manifest header.
+org.osgi.framework.security=Specifies the type of security manager the framework must use.
+osgi=Specifies that a security manager that supports all security aspects of the OSGi core specification including postponed conditions must be installed.
+org.osgi.framework.storage=Specified the persistent storage area used by the framework.
+org.osgi.framework.storage.clean=Specifies if and when the persistent storage area for the framework should be cleaned.
+onFirstInit=Specifies that the framework storage area must be cleaned before the framework is initialized for the first time.
+org.osgi.framework.library.extensions=Specifies a comma separated list of additional library file extensions that must be used when a bundle's class loader is searching for native libraries.
+org.osgi.framework.command.execpermission=Specifies an optional OS specific command to set file permissions on extracted native code.
+org.osgi.framework.trust.repositories=Specifies the trust repositories used by the framework.
+org.osgi.framework.windowsystem=Specifies the current windowing system.
+org.osgi.framework.startlevel.beginning=Specifies the beginning start level of the framework.
+org.osgi.framework.bundle.parent=Specifies the parent class loader type for all bundle class loaders.
+boot=Specifies to use of the boot class loader as the parent class loader for all bundle class loaders.
+app=Specifies to use the application class loader as the parent class loader for all bundle class loaders.
+ext=Specifies to use the extension class loader as the parent class loader for all bundle class loaders.
+framework=Specifies to use the framework class loader as the parent class loader for all bundle class loaders.
+objectClass=Service property identifying all of the class names under which a service was registered in the Framework.
+service.id=Service property identifying a service's registration number.
+service.pid=Service property identifying a service's persistent identifier.
+service.ranking=Service property identifying a service's ranking number.
+service.vendor=Service property identifying a service's vendor.
+service.description=Service property identifying a service's description.
+provide=The action string provide.
+require=The action string require.
+host=The action string host.
+fragment=The action string fragment.
+class=The action string class.
+execute=The action string execute.
+extensionLifecycle=The action string extensionLifecycle.
+lifecycle=The action string lifecycle.
+listener=The action string listener.
+metadata=The action string metadata.
+resolve=The action string resolve.
+resource=The action string resource.
+startlevel=The action string startlevel.
+context=The action string context.
+service.pid=The property key for the identifier of the application being scheduled.
+schedule.id=The property key for the schedule identifier.
+org.osgi.triggeringevent=The key for the startup argument used to pass the event object that triggered the schedule to launch the application instance.
+org/osgi/application/timer=The topic name for the virtual timer topic.
+year=The name of the year attribute of a virtual timer event.
+month=The name of the month attribute of a virtual timer event.
+day_of_month=The name of the day of month attribute of a virtual timer event.
+day_of_week=The name of the day of week attribute of a virtual timer event.
+hour_of_day=The name of the hour of day attribute of a virtual timer event.
+minute=The name of the minute attribute of a virtual timer event.
+service.pid=The property key for the unique identifier (PID) of the application instance.
+application.descriptor=The property key for the pid of the corresponding application descriptor.
+application.state=The property key for the state of this application instance.
+application.supports.exitvalue=The property key for the supports exit value property of this application instance.
+RUNNING=The application instance is running.
+STOPPING=The application instance is being stopped.
+application.name=The property key for the localized name of the application.
+application.icon=The property key for the localized icon of the application.
+service.pid=The property key for the unique identifier (PID) of the application.
+application.version=The property key for the version of the application.
+service.vendor=The property key for the name of the application vendor.
+application.visible=The property key for the visibility property of the application.
+application.launchable=The property key for the launchable property of the application.
+application.locked=The property key for the locked property of the application.
+application.description=The property key for the localized description of the application.
+application.documentation=The property key for the localized documentation of the application.
+application.copyright=The property key for the localized copyright notice of the application.
+application.license=The property key for the localized license of the application.
+application.container=The property key for the application container of the application.
+application.location=The property key for the location of the application.
+lifecycle=Allows the lifecycle management of the target applications.
+schedule=Allows scheduling of the target applications.
+lock=Allows setting/unsetting the locking state of the target applications.
+bundle.version=The version property defining the bundle on whose behalf a module context event has been issued.
+extender.bundle=The extender bundle property defining the extender bundle processing the module context for which an event has been issued.
+extender.bundle.id=The extender bundle id property defining the id of the extender bundle processing the module context for which an event has been issued.
+extender.bundle.symbolicName=The extender bundle symbolic name property defining the symbolic name of the extender bundle processing the module context for which an event has been issued.
+org/osgi/service/blueprint=Topic prefix for all events issued by the Blueprint Service
+org/osgi/service/blueprint/context/CREATING=Topic for Blueprint Service CREATING events
+org/osgi/service/blueprint/context/CREATED=Topic for Blueprint Service CREATED events
+org/osgi/service/blueprint/context/DESTROYING=Topic for Blueprint Service DESTROYING events
+org/osgi/service/blueprint/context/DESTROYED=Topic for Blueprint Service DESTROYED events
+org/osgi/service/blueprint/context/WAITING=Topic for Blueprint Service WAITING events
+org/osgi/service/blueprint/context/FAILURE=Topic for Blueprint Service FAILURE events
+singleton=
+prototype=
+bundle=
+cm.target=A service property to limit the Managed Service or Managed Service Factory configuration dictionaries a Configuration Plugin service receives.
+service.cmRanking=A service property to specify the order in which plugins are invoked.
+configure=The action string configure.
+service.factoryPid=Service property naming the Factory PID in the configuration dictionary.
+service.bundleLocation=Service property naming the location of the bundle that is associated with a a Configuration object.
+osgi.converter.classes=This property is a string, or array of strings, and defines the classes or interfaces that this converter recognizes.
+osgi.command.scope=The scope of commands provided by this service.
+osgi.command.function=A String, array, or list of method names that may be called for this command provider.
+Service-Component=Manifest header specifying the XML documents within a bundle that contain the bundle's Service Component descriptions.
+component.name=A component property for a component configuration that contains the name of the component as specified in the name attribute of the component element.
+component.id=A component property that contains the generated id for a component configuration.
+component.factory=A service registration property for a Component Factory that contains the value of the factory attribute.
+.target=The suffix for reference target properties.
+allow=This string is used to indicate that a row in the Conditional Permission Table should return an access decision of "allow" if the conditions are all satisfied and at least one of the permissions is implied.
+deny=This string is used to indicate that a row in the Conditional Permission Table should return an access decision of "deny" if the conditions are all satisfied and at least one of the permissions is implied.
+deploymentpackage.name=The name of the Deployment Package.
+deploymentpackage.readablename=The human readable name of the DP localized to the default locale.
+deploymentpackage.currentversion=The currently installed version of the Deployment Package.
+deploymentpackage.nextversion=The version of DP after the successful completion of the install operation (used in INSTALL event only).
+install=Constant String to the "install" action.
+list=Constant String to the "list" action.
+uninstall=Constant String to the "uninstall" action.
+uninstall_forced=Constant String to the "uninstall_forced" action.
+cancel=Constant String to the "cancel" action.
+metadata=Constant String to the "metadata" action.
+privatearea=Constant String to the "privatearea" action.
+-1=Return value from DriverSelector.select, if no Driver service should be attached to the Device service.
+DRIVER_ID=Property (named "DRIVER_ID") identifying a driver.
+DEVICE_CATEGORY=Property (named "DEVICE_CATEGORY") containing a human readable description of the device categories implemented by a device.
+DEVICE_SERIAL=Property (named "DEVICE_SERIAL") specifying a device's serial number.
+DEVICE_DESCRIPTION=Property (named "DEVICE_DESCRIPTION") containing a human readable string describing the actual hardware device.
+service.interface=Mandatory ServiceRegistration property which contains a collection of full qualified interface names offered by the advertised service endpoint.
+service.interface.version=Optional ServiceRegistration property which contains a collection of interface names with their associated version attributes separated by SEPARATOR e.g.
+osgi.remote.endpoint.interface=Optional ServiceRegistration property which contains a collection of interface names with their associated (non-Java) endpoint interface names separated by SEPARATOR e.g.: 'my.company.foo|MyWebService my.company.zoo|MyWebService'. This (non-Java) endpoint interface name is usually a communication protocol specific interface, for instance a web service interface name.
+service.properties=Optional ServiceRegistration property which contains a map of properties of the published service.
+osgi.remote.endpoint.location=Optional property of the published service identifying its location.
+osgi.remote.endpoint.id=Optional property of the published service uniquely identifying its endpoint.
+|=Separator constant for association of interface-specific values with the particular interface name.
+osgi.remote.discovery.product=Service Registration property for the name of the Discovery product.
+osgi.remote.discovery.product.version=Service Registration property for the version of the Discovery product.
+osgi.remote.discovery.vendor=Service Registration property for the Discovery product vendor name.
+osgi.remote.discovery.supported_protocols=Service Registration property that lists the discovery protocols used by this Discovery service.
+osgi.discovery.interest.interfaces=Optional ServiceRegistration property which contains service interfaces this tracker is interested in.
+osgi.discovery.interest.filters=Optional ServiceRegistration property which contains filters for services this tracker is interested in.
+osgi.remote.distribution.product=Service Registration property for the name of the Distribution Provider product.
+osgi.remote.distribution.product.version=Service Registration property for the version of the Distribution Provider product.
+osgi.remote.distribution.vendor=Service Registration property for the Distribution Provider product vendor name.
+osgi.remote.distribition.supported_intents=Service Registration property that lists the intents supported by this DistributionProvider.
+publish=The action string publish.
+subscribe=The action string subscribe.
+event.topics=Service registration property (named event.topics) specifying the Event topics of interest to a Event Handler service.
+event.filter=Service Registration property (named event.filter) specifying a filter to further select Event s of interest to a Event Handler service.
+bundle.signer=The Distinguished Names of the signers of the bundle relevant to the event.
+bundle.symbolicName=The Bundle Symbolic Name of the bundle relevant to the event.
+bundle.id=The Bundle id of the bundle relevant to the event.
+bundle=The Bundle object of the bundle relevant to the event.
+bundle.version=The version of the bundle relevant to the event.
+event=The forwarded event object.
+exception=An exception or error.
+exception.class=The name of the exception type.
+exception.message=The exception message.
+message=A human-readable message that is usually not localized.
+service=A service reference.
+service.id=A service's id.
+service.objectClass=A service's objectClass.
+service.pid=A service's persistent identity.
+timestamp=The time when the event occurred, as reported by System.currentTimeMillis().
+exception.class=This constant was released with an incorrectly spelled name.
+CompositeServiceFilter-Import=Manifest header (named "CompositeServiceFilter-Import") identifying the service filters that are used by a composite bundle to select services that will be registered into a child framework by its associated surrogate bundle.
+CompositeServiceFilter-Export=Manifest header (named "CompositeServiceFilter-Export") identifying the service filters that are used by a surrogate bundle to select services that will be registered into a parent framework by its associated composite bundle.
+org.osgi.service.http.authentication.remote.user=HttpServletRequest attribute specifying the name of the authenticated user.
+org.osgi.service.http.authentication.type=HttpServletRequest attribute specifying the scheme used in authentication.
+org.osgi.service.useradmin.authorization=HttpServletRequest attribute specifying the Authorization object obtained from the org.osgi.service.useradmin.UserAdmin service.
+io.scheme=Service property containing the scheme(s) for which this Connection Factory can create Connection objects.
+-1=Argument for getAttributeDefinitions(int).
+OSGI-INF/metatype=Location of meta type documents.
+read=Holders of MonitorPermission with the read action present are allowed to read the value of the StatusVariables specified in the permission's target field.
+reset=Holders of MonitorPermission with the reset action present are allowed to reset the value of the StatusVariables specified in the permission's target field.
+publish=Holders of MonitorPermission with the publish action present are Monitorable services that are allowed to publish the StatusVariables specified in the permission's target field.
+startjob=Holders of MonitorPermission with the startjob action present are allowed to initiate monitoring jobs involving the StatusVariables specified in the permission's target field.
+switchevents=Holders of MonitorPermission with the switchevents action present are allowed to switch event sending on or off for the value of the StatusVariables specified in the permission's target field.
+license=
+description=
+documentation=
+copyright=
+source=
+symbolicname=
+presentationname=
+id=
+version=
+url=
+size=
+provisioning.spid=The key to the provisioning information that uniquely identifies the Service Platform.
+provisioning.reference=The key to the provisioning information that contains the location of the provision data provider.
+provisioning.agent.config=The key to the provisioning information that contains the initial configuration information of the initial Management Agent.
+provisioning.update.count=The key to the provisioning information that contains the update count of the info data.
+provisioning.start.bundle=The key to the provisioning information that contains the location of the bundle to start with AllPermission.
+provisioning.rootx509=The key to the provisioning information that contains the root X509 certificate used to establish trust with operator when using HTTPS.
+provisioning.rsh.secret=The key to the provisioning information that contains the shared secret used in conjunction with the RSH protocol.
+text/plain;charset=utf-8=MIME type to be used in the InitialProvisioning-Entries header or stored in the extra field of a ZipEntry object for String data.
+application/octet-stream=MIME type to be used in the InitialProvisioning-Entries header or stored stored in the extra field of a ZipEntry object for byte[] data.
+application/vnd.osgi.bundle=MIME type to be used in the InitialProvisioning-Entries header or stored stored in the extra field of a ZipEntry object for an installable bundle file.
+application/x-osgi-bundle=Alternative MIME type to be used in the InitialProvisioning-Entries header or stored stored in the extra field of a ZipEntry object for an installable bundle file.
+text/x-osgi-bundle-url=MIME type to be stored in the extra field of a ZipEntry for a String that represents a URL for a bundle.
+InitialProvisioning-Entries=Name of the header that specifies the (MIME) type information for the ZIP file entries.
+ui1=Unsigned 1 Byte int.
+ui2=Unsigned 2 Byte int.
+ui4=Unsigned 4 Byte int.
+i1=1 Byte int.
+i2=2 Byte int.
+i4=4 Byte int.
+int=Integer number.
+r4=4 Byte float.
+r8=8 Byte float.
+number=Same as r8.
+fixed.14.4=Same as r8 but no more than 14 digits to the left of the decimal point and no more than 4 to the right.
+float=Floating-point number.
+char=Unicode string.
+string=Unicode string.
+date=A calendar date.
+dateTime=A specific instant of time.
+dateTime.tz=A specific instant of time.
+time=An instant of time that recurs every day.
+time.tz=An instant of time that recurs every day.
+boolean=True or false.
+bin.base64=MIME-style Base64 encoded binary BLOB.
+bin.hex=Hexadecimal digits representing octets.
+uri=Universal Resource Identifier.
+uuid=Universally Unique ID.
+UPnP.service.type=Property key for the optional service type uri.
+UPnP.service.id=Property key for the optional service id.
+upnp.filter=Key for a service property having a value that is an object of type org.osgi.framework.Filter and that is used to limit received events.
+UPnP=Constant for the value of the service property DEVICE_CATEGORY used for all UPnP devices.
+UPnP.export=The UPnP.export service property is a hint that marks a device to be picked up and exported by the UPnP Service.
+UPnP.device.UDN=Property key for the Unique Device Name (UDN) property.
+UPnP.device.UDN=Property key for the Unique Device ID property.
+UPnP.device.type=Property key for the UPnP Device Type property.
+UPnP.device.manufacturer=Mandatory property key for the device manufacturer's property.
+UPnP.device.modelName=Mandatory property key for the device model name.
+UPnP.device.friendlyName=Mandatory property key for a short user friendly version of the device name.
+UPnP.device.manufacturerURL=Optional property key for a URL to the device manufacturers Web site.
+UPnP.device.modelDescription=Optional (but recommended) property key for a String object with a long description of the device for the end user.
+UPnP.device.modelNumber=Optional (but recommended) property key for a String class typed property holding the model number of the device.
+UPnP.device.modelURL=Optional property key for a String typed property holding a string representing the URL to the Web site for this model.
+UPnP.device.serialNumber=Optional (but recommended) property key for a String typed property holding the serial number of the device.
+UPnP.device.UPC=Optional property key for a String typed property holding the Universal Product Code (UPC) of the device.
+UPnP.presentationURL=Optional (but recommended) property key for a String typed property holding a string representing the URL to a device representation Web page.
+UPnP.device.parentUDN=The property key that must be set for all embedded devices.
+UPnP.device.childrenUDN=The property key that must be set for all devices containing other embedded devices.
+url.handler.protocol=Service property naming the protocols serviced by a URLStreamHandlerService.
+url.content.mimetype=Service property naming the MIME types serviced by a java.net.ContentHandler.
+admin=The permission name "admin".
+changeProperty=The action string "changeProperty".
+changeCredential=The action string "changeCredential".
+getCredential=The action string "getCredential".
+user.anyone=The name of the predefined role, user.anyone, that all users and groups belong to.
+produce=The action string for the produce action.
+consume=The action string for the consume action.
+wireadmin.pid=Wire property key (named wireadmin.pid) specifying the persistent identity (PID) of this Wire object.
+wireadmin.producer.composite=A service registration property for a Producer service that is composite.
+wireadmin.consumer.composite=A service registration property for a Consumer service that is composite.
+wireadmin.producer.scope=Service registration property key (named wireadmin.producer.scope) specifying a list of names that may be used to define the scope of this Wire object.
+wireadmin.consumer.scope=Service registration property key (named wireadmin.consumer.scope) specifying a list of names that may be used to define the scope of this Wire object.
+wireadmin.producer.pid=Wire property key (named wireadmin.producer.pid) specifying the service.pid of the associated Producer service.
+wireadmin.consumer.pid=Wire property key (named wireadmin.consumer.pid) specifying the service.pid of the associated Consumer service.
+wireadmin.filter=Wire property key (named wireadmin.filter) specifying a filter used to control the delivery rate of data between the Producer and the Consumer service.
+wirevalue.current=Wire object's filter attribute (named wirevalue.current) representing the current value.
+wirevalue.previous=Wire object's filter attribute (named wirevalue.previous) representing the previous value.
+wirevalue.delta.absolute=Wire object's filter attribute (named wirevalue.delta.absolute) representing the absolute delta.
+wirevalue.delta.relative=Wire object's filter attribute (named wirevalue.delta.relative) representing the relative delta.
+wirevalue.elapsed=Wire object's filter attribute (named wirevalue.elapsed) representing the elapsed time, in ms, between this filter evaluation and the last update of the Consumer service.
+wireadmin.producer.filters=Service Registration property (named wireadmin.producer.filters).
+wireadmin.consumer.flavors=Service Registration property (named wireadmin.consumer.flavors) specifying the list of data types understood by this Consumer service.
+wireadmin.producer.flavors=Service Registration property (named wireadmin.producer.flavors) specifying the list of data types available from this Producer service.
+wireadmin.events=Service Registration property (named wireadmin.events) specifying the WireAdminEvent type of interest to a Wire Admin Listener service.
+javax.xml.parsers.SAXParserFactory=Filename containing the SAX Parser Factory Class name.
+javax.xml.parsers.DocumentBuilderFactory=Filename containing the DOM Parser Factory Class name.
+/META-INF/services/javax.xml.parsers.SAXParserFactory=Fully qualified path name of SAX Parser Factory Class Name file
+/META-INF/services/javax.xml.parsers.DocumentBuilderFactory=Fully qualified path name of DOM Parser Factory Class Name file
+parser.validating=Service property specifying if factory is configured to support validating parsers.
+parser.namespaceAware=Service property specifying if factory is configured to support namespace aware parsers.
diff --git a/bundleplugin/src/main/java/aQute/bnd/help/syntax.xml b/bundleplugin/src/main/java/aQute/bnd/help/syntax.xml
new file mode 100644
index 0000000..e81df31
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/help/syntax.xml
@@ -0,0 +1,2 @@
+
+Bundle-Name\\s*(:|=)\\s*
\ 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..2b0ce8a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/Make.java
@@ -0,0 +1,97 @@
+package aQute.bnd.make;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.header.*;
+
+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) {
+		StringBuilder sb = new StringBuilder();
+		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);
+		Parameters make = builder.parseHeader(s);
+		
+		for (Entry<String, Attrs> entry : make.entrySet()) {
+			String pattern = Processor.removeDuplicateMarker(entry.getKey());
+
+			Instruction instr = new Instruction(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..581deb7
--- /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 = argumentsOnMake.get("type");
+        if (!"bnd".equals(type))
+            return null;
+
+        String recipe = 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, 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..e06cdc5f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
@@ -0,0 +1,173 @@
+package aQute.bnd.make.calltree;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+
+/**
+ * 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.err.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>>(COMPARATOR);
+        final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> usedby = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>(COMPARATOR);
+
+        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
+     */
+    static Comparator<Clazz.MethodDef> COMPARATOR = new Comparator<Clazz.MethodDef>() {
+		
+		public int compare(MethodDef a, MethodDef b) {
+			int r =a.getName().compareTo(b.getName()); 
+			return  r != 0 ? r : a.getDescriptor().toString().compareTo(b.getDescriptor().toString());
+		}
+	};
+    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>(COMPARATOR));
+        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.getContainingClass().getFQN() + "'"
+                + getAccess(source.getAccess()) + 
+                ( source.isConstructor() ? "" :  " name='" + source.getName() + "'") + " descriptor='" + source.getDescriptor() + "' pretty='"
+                + source.toString() + "'" + 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..1a586b4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/component/ComponentAnnotationReader.java
@@ -0,0 +1,351 @@
+package aQute.bnd.make.component;
+
+import static aQute.lib.osgi.Constants.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.component.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+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");
+
+	static String[]				ACTIVATE_ARGUMENTS		= {
+		"org.osgi.service.component.ComponentContext", "org.osgi.framework.BundleContext",
+		Map.class.getName(), "org.osgi.framework.BundleContext" };
+	static String[]				OLD_ACTIVATE_ARGUMENTS	= { "org.osgi.service.component.ComponentContext" };
+	
+	Reporter					reporter				= new Processor();
+	MethodDef					method;
+	TypeRef						className;
+	Clazz						clazz;
+	TypeRef						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) {
+		String fqn = annotation.getName().getFQN();
+		
+		if (fqn.equals(Component.class.getName())) {
+			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].getBinary().equals("scala/ScalaObject"))
+							result.add(interfaces[i].getFQN());
+					}
+					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 (fqn.equals(Activate.class.getName())) {
+			if (!checkMethod())
+				setVersion(V1_1);
+
+			// TODO ... use the new descriptor to do better check
+
+			if (!ACTIVATEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+				reporter.error(
+						"Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+						className, method.getDescriptor());
+
+			if (method.getName().equals("activate")
+					&& OLDACTIVATEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches()) {
+				// this is the default!
+			} else {
+				setVersion(V1_1);
+				set(COMPONENT_ACTIVATE, method, "<>");
+			}
+
+		} else if (fqn.equals(Deactivate.class.getName())) {
+			if (!checkMethod())
+				setVersion(V1_1);
+
+			if (!ACTIVATEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+				reporter.error(
+						"Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+						className, method.getDescriptor());
+			if (method.getName().equals("deactivate")
+					&& OLDACTIVATEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches()) {
+				// This is the default!
+			} else {
+				setVersion(V1_1);
+				set(COMPONENT_DEACTIVATE, method, "<>");
+			}
+		} else if (fqn.equals(Modified.class.getName())) {
+			set(COMPONENT_MODIFIED, method, "<>");
+			setVersion(V1_1);
+		} else if (fqn.equals(Reference.class.getName())) {
+
+			String name = (String) annotation.get(Reference.class.getName());
+			String bind = method.getName();
+			String unbind = null;
+
+			if (name == null) {
+				Matcher m = BINDMETHOD.matcher(method.getName());
+				if (m.matches()) {
+					name = m.group(2).toLowerCase() + m.group(3);
+				} else {
+					name = method.getName().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(method.getDescriptor().toString());
+				if (m.matches()) {
+					service = Descriptors.binaryToFQN(m.group(1));
+				} else
+					throw new IllegalArgumentException(
+							"Cannot detect the type of a Component Reference from the descriptor: "
+									+ method.getDescriptor());
+			}
+
+			// 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(method.getDescriptor().toString()).matches()
+					|| !OLDBINDDESCRIPTOR.matcher(method.getDescriptor().toString()).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(method.getAccess()) || Modifier.isProtected(method.getAccess());
+	}
+
+	static Pattern	PROPERTY_PATTERN	= Pattern.compile("[^=]+=.+");
+
+	private void doProperties(aQute.lib.osgi.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, TypeRef name) {
+		className = name;
+	}
+
+	@Override public void implementsInterfaces(TypeRef[] interfaces) {
+		this.interfaces = interfaces;
+	}
+
+	@Override public void method(Clazz.MethodDef method) {
+		this.method = method;
+		descriptors.add(method.getName());
+	}
+
+	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..1e5989f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
@@ -0,0 +1,626 @@
+package aQute.bnd.make.component;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+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.QUERY;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.header.*;
+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 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);
+			Parameters sc = parseHeader(header);
+
+			for (Entry<String, Attrs> 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.ANNOTATED.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);
+
+			TypeRef implRef = analyzer.getTypeRefFromFQN(impl);
+			// Check if such a class exists
+			analyzer.referTo(implRef);
+
+			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)) {
+				TypeRef ref = analyzer.getTypeRefFromFQN(c);
+				Clazz clazz = analyzer.getClassspace().get(ref);
+				if (clazz != null) {
+					analyzer.referTo(ref);
+					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();
+					TypeRef ref = analyzer.getTypeRefFromFQN(interfaceName);
+					pw.println("    <provide interface='" + interfaceName + "'/>");
+					analyzer.referTo(ref);
+
+					// 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);
+				}
+				TypeRef ref = analyzer.getTypeRefFromFQN(interfaceName);
+				analyzer.referTo(ref);
+
+				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) {
+		StringBuilder sb = new StringBuilder();
+		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..020b85a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
@@ -0,0 +1,99 @@
+package aQute.bnd.make.coverage;
+
+import java.io.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+
+/**
+ * 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(TypeRef names[]) {
+                    MethodDef def = clazz.getMethodDef(0,
+                            "<implements>", "()V");
+                    // TODO
+                    for (TypeRef interfaceName : names) {
+                        for (Map.Entry<MethodDef, List<MethodDef>> entry : catalog
+                                .entrySet()) {
+                            String catalogClass = entry.getKey().getContainingClass().getFQN();
+                            List<MethodDef> references = entry.getValue();
+
+                            if (catalogClass.equals(interfaceName.getFQN())) {
+                                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>>(new Comparator<MethodDef>() {
+			public int compare(MethodDef a, MethodDef b) {
+				return a.getName().compareTo(b.getName());
+			}
+		});
+        for (final Clazz clazz : sources) {
+            clazz.parseClassFileWithCollector(new ClassDataCollector() {
+
+                public boolean classStart(int access, TypeRef name) {
+                    return clazz.isPublic();
+                }
+
+                public void method(MethodDef source) {
+                    if (source.isPublic()
+                            || source.isProtected())
+                        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..d879e53
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/coverage/CoverageResource.java
@@ -0,0 +1,91 @@
+package aQute.bnd.make.coverage;
+
+import static aQute.bnd.make.coverage.Coverage.*;
+
+import java.io.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+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().getContainingClass().getFQN();
+            if (!className.equals(currentClass)) {
+                classTag = new Tag("class");
+                classTag.addAttribute("name", className);
+                classTag.addAttribute("package", Descriptors.getPackage(className));
+                classTag.addAttribute("short", Descriptors.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.toString());
+        if (method.isPublic())
+            tag.addAttribute("public", true);
+        if (method.isStatic())
+            tag.addAttribute("static", true);
+        if (method.isProtected())
+            tag.addAttribute("protected", true);
+        if (method.isInterface())
+            tag.addAttribute("interface", true);
+        tag.addAttribute("constructor", method.isConstructor());
+        if (!method.isConstructor())
+            tag.addAttribute("name", method.getName());
+        tag.addAttribute("descriptor", method.getDescriptor());
+        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..ae541d6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetaTypeReader.java
@@ -0,0 +1,319 @@
+package aQute.bnd.make.metatype;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.metatype.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.lib.tag.*;
+import aQute.libg.generics.*;
+
+public class MetaTypeReader extends WriteResource {
+	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;
+	}
+
+	/**
+	 * @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.getGenericReturnType();
+		String id = Configurable.mangleMethodName(method.getName());
+		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.getClassName().getFQN(), method.getName(), method.getType().getFQN());
+			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;
+
+		TypeRef typeRef = reporter.getTypeRefFromFQN(rtype);
+		Clazz c = reporter.findClass(typeRef);
+		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.getName());
+				}
+			}
+		});
+		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;
+	}
+
+	class Find extends ClassDataCollector {
+		
+		@Override public void method(MethodDef mdef) {
+			method = mdef;
+			methods.put(mdef, null);
+		}
+		
+		@Override 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) {
+					MetaTypeReader.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();
+			}
+		}
+
+	}
+	
+
+
+	public void write(OutputStream out) throws IOException {
+		try {
+			finish();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+		PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
+		pw.println("<?xml version='1.0'?>");
+		metadata.print(0, pw);
+		pw.flush();
+	}
+
+	void finish() throws Exception {
+		if (!finished) {
+			finished = true;
+			clazz.parseClassFileWithCollector(new Find());
+			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.getClassName().getFQN();
+			String name = Clazz.unCamel(clazz.getClassName().getShortName());
+			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;
+	}
+
+	@Override public long lastModified() {
+		return 0;
+	}
+}
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..ff43613
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetatypePlugin.java
@@ -0,0 +1,36 @@
+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.QUERY;
+import aQute.libg.header.*;
+
+/**
+ * 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 {
+
+		Parameters map = analyzer.parseHeader(analyzer
+				.getProperty(Constants.METATYPE));
+
+		Jar jar = analyzer.getJar();
+		for (String name : map.keySet()) {
+			Collection<Clazz> metatypes = analyzer.getClasses("", QUERY.ANNOTATED.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..e9cbfc3
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
@@ -0,0 +1,618 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.maven.support.*;
+import aQute.bnd.maven.support.Pom.Scope;
+import aQute.bnd.settings.*;
+import aQute.lib.collections.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Descriptors.PackageRef;
+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;
+		}
+		LineCollection lc = new LineCollection(IO.reader(settings));
+		while (lc.hasNext()) {
+			System.err.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 = null;
+				try {
+					in = new FileInputStream(args[i++]);
+					properties.load(in);
+				} catch (Exception e) {
+				} finally {
+					if (in != null) {
+						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.err.println(command);
+		StringBuilder stdout = new StringBuilder();
+		StringBuilder stderr = new StringBuilder();
+		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);
+		}
+
+		StringBuilder out = new StringBuilder();
+		StringBuilder err = new StringBuilder();
+
+		System.err.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) {
+		Parameters map = Processor.parseHeader(
+				attr.getValue(Constants.BUNDLE_LICENSE), null);
+		if (map.isEmpty())
+			return null;
+
+		StringBuilder sb = new StringBuilder();
+		String sep = "Licensed under ";
+		for (Entry<String, Attrs> 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.err;
+		
+		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.err.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 = IO.writer(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.err.printf( "%20s %-20s %10s%n", dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
+					a.addClasspath(dep.getArtifact());
+				}
+				pw.println(a.getClasspath());
+				a.build();
+
+				TreeSet<PackageRef> sorted = new TreeSet<PackageRef>( a.getImports().keySet());
+				for ( PackageRef 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.err.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..52d2708
--- /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..ce936e7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeploy.java
@@ -0,0 +1,187 @@
+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.header.*;
+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 {
+		Parameters 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);
+				Parameters 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());
+
+		StringBuilder stdout = new StringBuilder();
+		StringBuilder stderr = new StringBuilder();
+
+		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);
+		}
+
+		StringBuilder out = new StringBuilder();
+		StringBuilder err = new StringBuilder();
+		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..bbd233f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeployCmd.java
@@ -0,0 +1,223 @@
+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.header.*;
+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;
+		}
+
+		/* skip first argument */
+		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 {
+		Parameters 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);
+				Parameters 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());
+
+		StringBuilder stdout = new StringBuilder();
+		StringBuilder stderr = new StringBuilder();
+
+		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);
+		}
+
+		StringBuilder out = new StringBuilder();
+		StringBuilder err = new StringBuilder();
+		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..a5ad200
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
@@ -0,0 +1,200 @@
+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 final 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 ) {
+			String version = f.getParentFile().getName();
+			version = Builder.cleanupVersion(version);
+			Version v = new Version( version );
+			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 = 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;
+	}
+
+	public String getLocation() {
+		return root.toString();
+	}
+}
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..5b8c149
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomFromManifest.java
@@ -0,0 +1,250 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.header.*;
+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 = IO.writer(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");
+
+			Parameters map = Processor.parseHeader(licenses, null);
+			for (Iterator<Entry<String, Attrs>> 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>
+
+				Entry<String, Attrs> 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 = 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..416062d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomResource.java
@@ -0,0 +1,162 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.header.*;
+
+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 = IO.writer(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");
+
+			Parameters map = Processor.parseHeader(licenses, null);
+			for (Iterator<Entry<String, Attrs>> 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>
+
+				Entry<String, Attrs> 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 = 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..60ebb63
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenEntry.java
@@ -0,0 +1,341 @@
+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.err.println("Downloading "  + repo + " path " + path + " url " + url);
+			File file = new File(root, path);
+			IO.copy(url.openStream(), file);
+			System.err.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()) {
+				FileInputStream in = null;
+				try {
+					in = new FileInputStream(props);
+					properties.load(in);
+				} catch (Exception e) {
+					// we ignore for now, will handle it on safe
+				} finally {
+					IO.close(in);
+				}
+			}
+		}
+		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.err.println("Verified ok " + actualFile + " digest " + algorithm);
+				return true;
+			}
+		}
+		System.err.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..a9f0ba2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
@@ -0,0 +1,137 @@
+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;
+	}
+
+	public String getLocation() {
+		if ( repositories == null || repositories.length==0)
+			return "maven central";
+		
+		return Arrays.toString(repositories); 
+	}
+}
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..30a3378
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/Pom.java
@@ -0,0 +1,351 @@
+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.*;
+
+import aQute.lib.io.*;
+
+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>();
+	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.err.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;
+	}
+
+	static 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.err.println("Cannot find " + dep + " from "
+									+ rover.previous.dependency);
+						else
+							System.err.println("Cannot find " + dep + " from top");
+				} catch (Exception e) {
+					if (rover.previous != null)
+						System.err.println("Cannot find " + dep + " from "
+								+ rover.previous.dependency);
+					else
+						System.err.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.err.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 = IO.writer(file);
+		try {
+			doEntry(writer, this);
+			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..069acb9
--- /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.err.print(indent);
+//		System.err.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;
+	
+	}
+
+	static 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.err.println("Replce: " + in);
+		if ( in == null) {
+			System.err.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..312825e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/repo/eclipse/EclipseRepo.java
@@ -0,0 +1,205 @@
+package aQute.bnd.repo.eclipse;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class EclipseRepo implements Plugin, RepositoryPlugin {
+    File                             root;
+    Reporter                         reporter;
+    String                           name;
+    Parameters index;
+
+    public final static String             LOCATION = "location";
+    public final static String             NAME     = "name";
+
+    public void setProperties(Map<String, String> map) {
+        String location = 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 = map.get(NAME);
+
+        try {
+            index = buildIndex();
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Could not build index for eclipse repo: " + root);
+        }
+    }
+
+    Parameters 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()) {
+
+                    Parameters 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 = IO.reader(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, ? extends Map<String, String>> map)
+            throws Exception {
+        String s = Processor.printClauses(map);
+        index.getParentFile().mkdirs();
+        PrintWriter fw = IO.writer(index);
+        try {
+            fw.write(s);
+        } finally {
+            fw.close();
+        }
+    }
+
+    private Parameters buildIndex(File[] plugins) {
+        Parameters map = new Parameters();
+        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 (Entry<String, String> entry : instances.entrySet()) {
+            if (r.includes(new Version(entry.getKey()))) {
+                File f = new File(entry.getValue());
+                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 = new Instruction(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;
+	}
+
+	public String getLocation() {
+		return root.toString();
+	}
+}
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..e937110
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/BndListener.java
@@ -0,0 +1,23 @@
+package aQute.bnd.service;
+
+import java.io.*;
+import java.util.concurrent.atomic.*;
+
+import aQute.libg.reporter.*;
+
+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;
+    }
+    
+    public void signal(Reporter reporter) {
+    	
+    }
+}
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/IndexProvider.java b/bundleplugin/src/main/java/aQute/bnd/service/IndexProvider.java
new file mode 100644
index 0000000..5454c2d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/IndexProvider.java
@@ -0,0 +1,13 @@
+package aQute.bnd.service;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Set;
+
+public interface IndexProvider {
+
+	List<URL> getIndexLocations() throws Exception;
+
+	Set<ResolutionPhase> getSupportedPhases();
+
+}
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..9ba5b04
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/OBRIndexProvider.java
@@ -0,0 +1,11 @@
+package aQute.bnd.service;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+@Deprecated
+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..78f9801
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/OBRResolutionMode.java
@@ -0,0 +1,6 @@
+package aQute.bnd.service;
+
+@Deprecated
+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 100755
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..e441a4c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java
@@ -0,0 +1,20 @@
+package aQute.bnd.service;
+
+import java.io.*;
+import java.util.*;
+
+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;
+	
+	File getCacheDirectory();
+}
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..13899f7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryListenerPlugin.java
@@ -0,0 +1,16 @@
+package aQute.bnd.service;
+
+import java.io.*;
+
+import aQute.lib.osgi.*;
+
+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..91b0a7e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java
@@ -0,0 +1,91 @@
+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();
+
+	/**
+	 * Return a location identifier of this repository
+	 */
+
+	String getLocation();
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/ResolutionPhase.java b/bundleplugin/src/main/java/aQute/bnd/service/ResolutionPhase.java
new file mode 100644
index 0000000..0fd7ec9
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/ResolutionPhase.java
@@ -0,0 +1,5 @@
+package aQute.bnd.service;
+
+public enum ResolutionPhase {
+	build, runtime
+}
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..be7f79b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/ResourceHandle.java
@@ -0,0 +1,12 @@
+package aQute.bnd.service;
+
+import java.io.*;
+
+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/diff/Delta.java b/bundleplugin/src/main/java/aQute/bnd/service/diff/Delta.java
new file mode 100644
index 0000000..eec4781
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/diff/Delta.java
@@ -0,0 +1,19 @@
+package aQute.bnd.service.diff;
+
+/**
+ * The Delta provides information about the {@link Diff} object. It tells the
+ * relation between the newer and older compared elements.
+ * 
+ */
+public enum Delta {
+
+	// ORDER IS IMPORTANT FOR TRANSITIONS TABLE!
+
+	/**
+	 * 
+	 */
+	IGNORED, // for all
+	UNCHANGED, CHANGED, MICRO, MINOR, MAJOR, // content
+	REMOVED, ADDED; // structural
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/diff/Diff.java b/bundleplugin/src/main/java/aQute/bnd/service/diff/Diff.java
new file mode 100644
index 0000000..43bf4e5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/diff/Diff.java
@@ -0,0 +1,23 @@
+package aQute.bnd.service.diff;
+
+import java.util.*;
+
+public interface Diff {
+	interface Ignore {
+		boolean contains(Diff diff);
+	}
+	
+	Delta getDelta();
+	Delta getDelta(Ignore ignore);
+
+	Type getType();
+	String getName();
+	Tree getOlder();
+	Tree getNewer();
+
+	Collection<? extends Diff> getChildren();
+	
+	Diff get(String name);
+	
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/diff/Differ.java b/bundleplugin/src/main/java/aQute/bnd/service/diff/Differ.java
new file mode 100644
index 0000000..869a237
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/diff/Differ.java
@@ -0,0 +1,13 @@
+package aQute.bnd.service.diff;
+
+import aQute.lib.osgi.*;
+
+/**
+ * Compare two Jars and report the differences.
+ */
+public interface Differ {
+	Tree tree(Analyzer source ) throws Exception;
+	Tree tree(Jar source) throws Exception;
+
+	Tree deserialize(Tree.Data data) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/diff/Tree.java b/bundleplugin/src/main/java/aQute/bnd/service/diff/Tree.java
new file mode 100644
index 0000000..7072ac1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/diff/Tree.java
@@ -0,0 +1,29 @@
+package aQute.bnd.service.diff;
+
+public interface Tree {
+
+	public class Data {
+		public String	name;
+		public Type		type		= Type.METHOD;
+		public Delta	add			= Delta.MINOR;
+		public Delta	rem			= Delta.MAJOR;
+		public Data[]	children	= null;
+		public String	comment		= null;
+	}
+
+	Data serialize();
+
+	Tree[] getChildren();
+
+	String getName();
+
+	Type getType();
+
+	Delta ifAdded();
+
+	Delta ifRemoved();
+
+	Diff diff(Tree older);
+
+	Tree get(String name);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/diff/Type.java b/bundleplugin/src/main/java/aQute/bnd/service/diff/Type.java
new file mode 100644
index 0000000..d71b9f4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/diff/Type.java
@@ -0,0 +1,10 @@
+package aQute.bnd.service.diff;
+
+public enum Type {
+	ACCESS, BUNDLE, API, MANIFEST, PACKAGE, CLASS, INTERFACE, ANNOTATION, ENUM, EXTENDS, IMPLEMENTS, FIELD, METHOD, ANNOTATED, PROPERTY, RESOURCE, CUSTOM, CLAUSE, HEADER, PARAMETER, CLASS_VERSION, RESOURCES, CONSTANT, RETURN, VERSION, DEPRECATED;
+
+	public boolean isInherited() {
+		// TODO Auto-generated method stub
+		return false;
+	} 
+}
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..0ff7674
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/packageinfo
@@ -0,0 +1 @@
+version 1.45.0
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/url/TaggedData.java b/bundleplugin/src/main/java/aQute/bnd/service/url/TaggedData.java
new file mode 100644
index 0000000..53332f4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/url/TaggedData.java
@@ -0,0 +1,37 @@
+package aQute.bnd.service.url;
+
+import java.io.InputStream;
+
+/**
+ * Represents a data stream that has a tag associated with it; the primary
+ * use-case is an HTTP response stream with an ETag header.
+ * 
+ * @author Neil Bartlett
+ * 
+ */
+public class TaggedData {
+
+	private final String tag;
+	private final InputStream inputStream;
+
+	public TaggedData(String tag, InputStream inputStream) {
+		this.tag = tag;
+		this.inputStream = inputStream;
+	}
+
+	/**
+	 * Returns the ETag for the retrieved resource, or {@code null} if the ETag
+	 * was not provided by the server.
+	 */
+	public String getTag() {
+		return tag;
+	}
+
+	/**
+	 * Returns the input stream containing the resource data.
+	 */
+	public InputStream getInputStream() {
+		return inputStream;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/url/URLConnector.java b/bundleplugin/src/main/java/aQute/bnd/service/url/URLConnector.java
new file mode 100644
index 0000000..e853509
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/url/URLConnector.java
@@ -0,0 +1,49 @@
+package aQute.bnd.service.url;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+public interface URLConnector {
+
+	/**
+	 * Connect to the specified URL.
+	 * 
+	 * @param url
+	 * @return
+	 * @throws IOException
+	 */
+	InputStream connect(URL url) throws IOException;
+
+	/**
+	 * Connect to the specified URL, also returning the ETag if available.
+	 * 
+	 * @param url
+	 *            The remote URL.
+	 * @return An instance of {@link TaggedData}; note that the
+	 *         {@link TaggedData#getTag()} method <strong>may</strong> return
+	 *         {@code null} if the resource has no tag.
+	 * @throws IOException
+	 * 
+	 * @since 1.1
+	 */
+	TaggedData connectTagged(URL url) throws IOException;
+
+	/**
+	 * Connect to the specified URL while providing the last known tag for the
+	 * remote resource; the response will be {@code null} if the remote resource
+	 * is unchanged.
+	 * 
+	 * @param url
+	 *            The remote URL.
+	 * @param tag
+	 *            The last known tag value for the resource.
+	 * @return An instance of {@link TaggedData}, or {@code null} if the
+	 *         resource has not modified (i.e., if it has the same tag value).
+	 * @throws IOException
+	 * 
+	 * @since 1.1
+	 */
+	TaggedData connectTagged(URL url, String tag) throws IOException;
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/url/packageinfo b/bundleplugin/src/main/java/aQute/bnd/service/url/packageinfo
new file mode 100644
index 0000000..7ae9673
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/url/packageinfo
@@ -0,0 +1 @@
+version 1.1
\ No newline at end of file
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..d0073b5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/signing/JartoolSigner.java
@@ -0,0 +1,144 @@
+package aQute.bnd.signing;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+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;
+    String digestalg;
+
+    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");
+        if (map.containsKey("digestalg"))
+            this.digestalg = map.get("digestalg");
+    }
+
+    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);
+        }
+
+        if (digestalg != null) {
+        	command.add("-digestalg");
+        	command.add(digestalg);
+	    }
+
+        command.add(tmp.getAbsolutePath());
+        command.add(alias);
+        builder.trace("Jarsigner command: %s", command);
+        command.setTimeout(20, TimeUnit.SECONDS);
+        StringBuilder out = new StringBuilder();
+        StringBuilder err = new StringBuilder();
+        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 (Entry<String, Resource> entry : dir.entrySet()) {
+            String path = entry.getKey();
+            if (path.matches(".*\\.(DSA|RSA|SF|MF)$")) {
+                jar.putResource(path, entry.getValue());
+            }
+        }
+        jar.setDoNotTouchManifest();
+    }
+
+    StringBuilder collect(final InputStream in) throws Exception {
+        final StringBuilder sb = new StringBuilder();
+        
+        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..1827cd6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/signing/Signer.java
@@ -0,0 +1,196 @@
+package aQute.bnd.signing;
+
+import java.io.*;
+import java.security.*;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.base64.*;
+import aQute.lib.io.*;
+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;
+
+            java.io.FileInputStream keystoreInputStream = null;
+            try {
+            	keystoreInputStream = new java.io.FileInputStream(
+                        keystoreFile);
+                char[] pw = password == null ? new char[0]
+                        : password.toCharArray();
+                
+                keystore.load(keystoreInputStream, pw);
+                keystoreInputStream.close();
+                privateKeyEntry =  (PrivateKeyEntry) keystore.getEntry(
+                        alias, new KeyStore.PasswordProtection(pw));
+            } catch (Exception e) {
+                error("No able to load the private key from the give keystore("+keystoreFile.getAbsolutePath()+") with alias "+alias+" : "
+                        + e);
+                return;
+            } finally {
+            	IO.close(keystoreInputStream);
+            }
+            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) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        PrintWriter ps = IO.writer(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("UTF-8"));
+                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;
+    }
+}