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

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1185095 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/Make.java b/bundleplugin/src/main/java/aQute/bnd/make/Make.java
new file mode 100644
index 0000000..6827c6d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/Make.java
@@ -0,0 +1,99 @@
+package aQute.bnd.make;
+
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class Make {
+    Builder                               builder;
+    Map<Instruction, Map<String, String>> make;
+
+    public Make(Builder builder) {
+        this.builder = builder;
+    }
+
+    public Resource process(String source) {
+        Map<Instruction, Map<String, String>> make = getMakeHeader();
+        builder.trace("make " + source);
+
+        for (Map.Entry<Instruction, Map<String, String>> entry : make
+                .entrySet()) {
+            Instruction instr = entry.getKey();
+            Matcher m = instr.getMatcher(source);
+            if (m.matches() || instr.isNegated()) {
+                Map<String, String> arguments = replace(m, entry.getValue());
+                List<MakePlugin> plugins = builder.getPlugins(MakePlugin.class);
+                for (MakePlugin plugin : plugins) {
+                    try {
+                        Resource resource = plugin.make(builder,
+                                source, arguments);
+                        if (resource != null) {
+                            builder.trace("Made " + source + " from args "
+                                    + arguments + " with " + plugin);
+                            return resource;
+                        }
+                    } catch (Exception e) {
+                        builder.error("Plugin " + plugin
+                                + " generates error when use in making "
+                                + source + " with args " + arguments, e);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private Map<String, String> replace(Matcher m, Map<String, String> value) {
+        Map<String, String> newArgs = Processor.newMap();
+        for (Map.Entry<String, String> entry : value.entrySet()) {
+            String s = entry.getValue();
+            s = replace(m, s);
+            newArgs.put(entry.getKey(), s);
+        }
+        return newArgs;
+    }
+
+    String replace(Matcher m, CharSequence s) {
+        StringBuffer sb = new StringBuffer();
+        int max = '0' + m.groupCount() + 1;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '$' && i < s.length() - 1) {
+                c = s.charAt(++i);
+                if (c >= '0' && c <= max) {
+                    int index = c - '0';
+                    String replacement = m.group(index);
+                    if (replacement != null)
+                        sb.append(replacement);
+                } else {
+                    if (c == '$')
+                        i++;
+                    sb.append(c);
+                }
+            } else
+                sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    Map<Instruction, Map<String, String>> getMakeHeader() {
+        if (make != null)
+            return make;
+        make = Processor.newMap();
+
+        String s = builder.getProperty(Builder.MAKE);
+        Map<String, Map<String, String>> make = builder.parseHeader(s);
+        for (Iterator<Map.Entry<String, Map<String, String>>> e = make
+                .entrySet().iterator(); e.hasNext();) {
+            Map.Entry<String, Map<String, String>> entry = e.next();
+            String pattern = Processor.removeDuplicateMarker(entry.getKey());
+            
+            Instruction instr = Instruction.getPattern(pattern);
+            this.make.put(instr, entry.getValue());
+        }
+
+        return this.make;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java b/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java
new file mode 100644
index 0000000..f2e90d8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/MakeBnd.java
@@ -0,0 +1,65 @@
+package aQute.bnd.make;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.build.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class MakeBnd implements MakePlugin, Constants {
+    final static Pattern JARFILE = Pattern.compile("(.+)\\.(jar|ipa)");
+
+    public Resource make(Builder builder, String destination,
+            Map<String, String> argumentsOnMake) throws Exception {
+        String type = (String) argumentsOnMake.get("type");
+        if (!"bnd".equals(type))
+            return null;
+
+        String recipe = (String) argumentsOnMake.get("recipe");
+        if (recipe == null) {
+            builder.error("No recipe specified on a make instruction for "
+                    + destination);
+            return null;
+        }
+        File bndfile = builder.getFile(recipe);
+        if (bndfile.isFile()) {
+            // We do not use a parent because then we would
+            // build ourselves again. So we can not blindly
+            // inherit the properties.
+            Builder bchild = builder.getSubBuilder();
+            bchild.removeBundleSpecificHeaders();
+            
+            // We must make sure that we do not include ourselves again!
+            bchild.setProperty(Analyzer.INCLUDE_RESOURCE, "");
+            bchild.setProperty(Analyzer.INCLUDERESOURCE, "");
+            bchild.setProperties(bndfile, builder.getBase());
+            
+            Jar jar = bchild.build();
+            Jar dot = builder.getTarget();
+
+            if (builder.hasSources()) {
+                for (String key : jar.getResources().keySet()) {
+                    if (key.startsWith("OSGI-OPT/src"))
+                        dot.putResource(key, (Resource) jar.getResource(key));
+                }
+            }
+            builder.getInfo(bchild, bndfile.getName() +": ");
+            String debug = bchild.getProperty(DEBUG);
+            if (Processor.isTrue(debug)) {
+                if ( builder instanceof ProjectBuilder ) {
+                    ProjectBuilder pb = (ProjectBuilder) builder;
+                    File target = pb.getProject().getTarget();
+                    String bsn = bchild.getBsn();
+                    File output = new File(target, bsn+".jar");
+                    jar.write(output);
+                    pb.getProject().getWorkspace().changedFile(output);
+                }
+            }
+            return new JarResource(jar);
+        } else
+            return null;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java b/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java
new file mode 100644
index 0000000..3d5e4c8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/MakeCopy.java
@@ -0,0 +1,45 @@
+package aQute.bnd.make;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class MakeCopy implements MakePlugin {
+
+    public Resource make(Builder builder, String destination,
+            Map<String, String> argumentsOnMake) throws Exception {
+        String type = argumentsOnMake.get("type");
+        if (!type.equals("copy"))
+            return null;
+
+        String from = argumentsOnMake.get("from");
+        if (from == null) {
+            String content = argumentsOnMake.get("content");
+            if (content == null)
+                throw new IllegalArgumentException(
+                        "No 'from' or 'content' field in copy "
+                                + argumentsOnMake);
+            return new EmbeddedResource(content.getBytes("UTF-8"),0);
+        } else {
+
+            File f = builder.getFile(from);
+            if (f.isFile())
+                return new FileResource(f);
+            else {
+                try {
+                    URL url = new URL(from);
+                    return new URLResource(url);
+                } catch(MalformedURLException mfue) {
+                    // We ignore this
+                }
+                throw new IllegalArgumentException(
+                        "Copy source does not exist " + from
+                                + " for destination " + destination);
+            }
+        }
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java b/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
new file mode 100644
index 0000000..2775a57
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
@@ -0,0 +1,165 @@
+package aQute.bnd.make.calltree;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+/**
+ * Create an XML call tree of a set of classes. The structure of the XML is:
+ * 
+ * <pre>
+ *    calltree ::= &lt;using&gt; &lt;usedby&gt;
+ *    using    ::= &lt;method&gt; *
+ *    usedby   ::= &lt;method&gt; *
+ *    method   ::= &lt;ref&gt; 
+ * </pre>
+ * 
+ * The <code>using</code> element contains methods in the set of classes and
+ * their references. The <code>usedby</code> element contains the used methods
+ * and their references to the set of classes. The <code>ref</code> element
+ * contains the class, the method name, the descriptor, and a pretty print
+ * version of the method.
+ * 
+ * The XML does not contain an XML processor instruction to make it easier to
+ * include in other XML. The encoding is always UTF-8.
+ * 
+ * This class can be used as a resource, just add it to a JAR and the data is
+ * generated when the resource is written (saving time when the JAR is up to
+ * date and does not have to be generated). However, the actual write method is
+ * a static method and can be called as well:
+ * {@link #writeCalltree(PrintWriter, Collection)}.
+ */
+public class CalltreeResource extends WriteResource {
+    Collection<Clazz> classes;
+
+    /**
+     * Create a resource for inclusion that will print a call tree.
+     * 
+     * @param values the classes for which the call tree is generated.
+     */
+    public CalltreeResource(Collection<Clazz> values) {
+        this.classes = values;
+        System.out.println(values);
+    }
+
+    /**
+     * We set the last modified to 0 so this resource does not force
+     * a new JAR if all other resources are up to date.
+     */
+    public long lastModified() {
+        return 0;
+    }
+
+    /**
+     * The write method is called to write the resource. We just call the static
+     * method.
+     */
+    public void write(OutputStream out) throws Exception {
+        OutputStreamWriter osw = new OutputStreamWriter(out, Constants.DEFAULT_CHARSET);
+        PrintWriter pw = new PrintWriter(osw);
+        try {
+            writeCalltree(pw, classes);
+        } finally {
+            pw.flush();
+        }
+    }
+
+    /**
+     * Print the call tree in XML.
+     * 
+     * @param out The output writer
+     * @param classes The set of classes
+     * @throws IOException Any errors
+     */
+    public static void writeCalltree(PrintWriter out, Collection<Clazz> classes)
+            throws Exception {
+
+        final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> using = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
+        final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> usedby = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
+
+        ClassDataCollector cd = new ClassDataCollector() {
+            Clazz.MethodDef source;
+
+            // Before a method is parsed
+            public void method(Clazz.MethodDef source) {
+                this.source = source;
+                xref(using, source, null);
+                xref(usedby, source, null);
+            }
+
+            // For any reference in the previous method.
+            public void reference(Clazz.MethodDef reference) {
+                xref(using, source, reference);
+                xref(usedby, reference, source);
+            }
+        };
+        for (Clazz clazz : classes) {
+            clazz.parseClassFileWithCollector(cd);
+        }
+
+        out.println("<calltree>");
+        xref(out, "using", using);
+        xref(out, "usedby", usedby);
+        out.println("</calltree>");
+    }
+
+    /*
+     * Add a new reference
+     */
+    private static void xref(
+            Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references,
+            Clazz.MethodDef source, Clazz.MethodDef reference) {
+        Set<Clazz.MethodDef> set = references.get(source);
+        if (set == null)
+            references.put(source, set=new TreeSet<Clazz.MethodDef>());
+        if ( reference != null)
+            set.add(reference);
+    }
+
+    /*
+     * Print out either using or usedby sets
+     */
+    private static void xref(PrintWriter out, String group,
+            Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references) {
+        out.println("  <" + group + ">");
+        for (Map.Entry<Clazz.MethodDef, Set<Clazz.MethodDef>> entry : references
+                .entrySet()) {
+            Clazz.MethodDef source = entry.getKey();
+            Set<Clazz.MethodDef> refs = entry.getValue();
+            method(out, "method", source, ">");
+            for (Clazz.MethodDef ref : refs) {
+                method(out, "ref", ref, "/>");
+            }
+            out.println("      </method>");
+        }
+        out.println("  </" + group + ">");
+    }
+
+    /*
+     * Print out a method.
+     */
+    private static void method(PrintWriter out, String element,
+            Clazz.MethodDef source, String closeElement) {
+        out.println("      <" + element + " class='" + source.clazz + "'"
+                + getAccess(source.access) + 
+                ( source.isConstructor() ? "" :  " name='" + source.name + "'") + " descriptor='" + source.descriptor + "' pretty='"
+                + source.getPretty() + "'" + closeElement);
+    }
+
+    private static String getAccess(int access) {
+        StringBuilder sb = new StringBuilder();
+        if ( Modifier.isPublic(access) )
+            sb.append(" public='true'");
+        if ( Modifier.isStatic(access) )
+            sb.append(" static='true'");
+        if ( Modifier.isProtected(access) )
+            sb.append(" protected='true'");
+        if ( Modifier.isInterface(access) )
+            sb.append(" interface='true'");
+        
+        return sb.toString();
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/component/ComponentAnnotationReader.java b/bundleplugin/src/main/java/aQute/bnd/make/component/ComponentAnnotationReader.java
new file mode 100644
index 0000000..a73e714
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/component/ComponentAnnotationReader.java
@@ -0,0 +1,343 @@
+package aQute.bnd.make.component;
+
+import static aQute.bnd.make.component.ServiceComponent.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.component.*;
+import aQute.lib.osgi.*;
+import aQute.libg.reporter.*;
+
+public class ComponentAnnotationReader extends ClassDataCollector {
+	String						EMPTY[]					= new String[0];
+	private static final String	V1_1					= "1.1.0";																																// "1.1.0"
+	static Pattern				BINDDESCRIPTOR			= Pattern
+																.compile("\\(L([^;]*);(Ljava/util/Map;|Lorg/osgi/framework/ServiceReference;)*\\)V");
+	static Pattern				BINDMETHOD				= Pattern.compile("(set|bind|add)(.)(.*)");
+
+	static Pattern				ACTIVATEDESCRIPTOR		= Pattern
+																.compile("\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)V");
+	static Pattern				OLDACTIVATEDESCRIPTOR	= Pattern
+																.compile("\\(Lorg/osgi/service/component/ComponentContext;\\)V");
+	static Pattern				OLDBINDDESCRIPTOR		= Pattern.compile("\\(L([^;]*);\\)V");
+	static Pattern				REFERENCEBINDDESCRIPTOR	= Pattern
+																.compile("\\(Lorg/osgi/framework/ServiceReference;\\)V");
+
+	Reporter					reporter				= new Processor();
+	String						method;
+	String						methodDescriptor;
+	int							methodAccess;
+	String						className;
+	Clazz						clazz;
+	String						interfaces[];
+	Set<String>					multiple				= new HashSet<String>();
+	Set<String>					optional				= new HashSet<String>();
+	Set<String>					dynamic					= new HashSet<String>();
+
+	Map<String, String>			map						= new TreeMap<String, String>();
+	Set<String>					descriptors				= new HashSet<String>();
+	List<String>				properties				= new ArrayList<String>();
+	String						version					= null;
+
+	// TODO make patterns for descriptors
+
+	ComponentAnnotationReader(Clazz clazz) {
+		this.clazz = clazz;
+	}
+
+	public void setReporter(Reporter reporter) {
+		this.reporter = reporter;
+	}
+
+	public Reporter getReporter() {
+		return this.reporter;
+	}
+
+	public static Map<String, String> getDefinition(Clazz c) throws Exception {
+		return getDefinition(c, new Processor());
+	}
+
+	public static Map<String, String> getDefinition(Clazz c, Reporter reporter) throws Exception {
+		ComponentAnnotationReader r = new ComponentAnnotationReader(c);
+		r.setReporter(reporter);
+		c.parseClassFileWithCollector(r);
+		r.finish();
+		return r.map;
+	}
+
+	public void annotation(Annotation annotation) {
+
+		if (annotation.getName().equals(Component.RNAME)) {
+			set(COMPONENT_NAME, annotation.get(Component.NAME), "<>");
+			set(COMPONENT_FACTORY, annotation.get(Component.FACTORY), false);
+			setBoolean(COMPONENT_ENABLED, annotation.get(Component.ENABLED), true);
+			setBoolean(COMPONENT_IMMEDIATE, annotation.get(Component.IMMEDIATE), false);
+			setBoolean(COMPONENT_SERVICEFACTORY, annotation.get(Component.SERVICEFACTORY), false);
+
+			if (annotation.get(Component.DESIGNATE) != null) {
+				String configs = annotation.get(Component.DESIGNATE);
+				if (configs != null) {
+					set(COMPONENT_DESIGNATE, Clazz.objectDescriptorToFQN(configs), "");
+				}
+			}
+
+			if (annotation.get(Component.DESIGNATE_FACTORY) != null) {
+				String configs = annotation.get(Component.DESIGNATE_FACTORY);
+				if (configs != null) {
+					set(COMPONENT_DESIGNATEFACTORY, Clazz.objectDescriptorToFQN(configs), "");
+				}
+			}
+
+			setVersion((String) annotation.get(Component.VERSION));
+
+			String configurationPolicy = annotation.get(Component.CONFIGURATION_POLICY);
+			if (configurationPolicy != null)
+				set(COMPONENT_CONFIGURATION_POLICY, configurationPolicy.toLowerCase(), "");
+
+			doProperties(annotation);
+
+			Object[] provides = (Object[]) annotation.get(Component.PROVIDE);
+			String[] p;
+			if (provides == null) {
+				// Use the found interfaces, but convert from internal to
+				// fqn.
+				if (interfaces != null) {
+					List<String> result = new ArrayList<String>();
+					for (int i = 0; i < interfaces.length; i++) {
+						if (!interfaces[i].equals("scala/ScalaObject") )
+							result.add(Clazz.internalToFqn(interfaces[i]));
+					}
+					p = result.toArray(EMPTY);
+				} else
+					p = EMPTY;
+			} else {
+				// We have explicit interfaces set
+				p = new String[provides.length];
+				for (int i = 0; i < provides.length; i++) {
+					p[i] = descriptorToFQN(provides[i].toString());
+				}
+			}
+			if (p.length > 0) {
+				set(COMPONENT_PROVIDE, Processor.join(Arrays.asList(p)), "<>");
+			}
+
+		} else if (annotation.getName().equals(Activate.RNAME)) {
+			if (!checkMethod())
+				setVersion(V1_1);
+
+			if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
+				reporter.error(
+						"Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+						className, methodDescriptor);
+
+			if (method.equals("activate")
+					&& OLDACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches()) {
+				// this is the default!
+			} else {
+				setVersion(V1_1);
+				set(COMPONENT_ACTIVATE, method, "<>");
+			}
+
+		} else if (annotation.getName().equals(Deactivate.RNAME)) {
+			if (!checkMethod())
+				setVersion(V1_1);
+
+			if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
+				reporter.error(
+						"Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+						className, methodDescriptor);
+			if (method.equals("deactivate")
+					&& OLDACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches()) {
+				// This is the default!
+			} else {
+				setVersion(V1_1);
+				set(COMPONENT_DEACTIVATE, method, "<>");
+			}
+		} else if (annotation.getName().equals(Modified.RNAME)) {
+			set(COMPONENT_MODIFIED, method, "<>");
+			setVersion(V1_1);
+		} else if (annotation.getName().equals(Reference.RNAME)) {
+
+			String name = (String) annotation.get(Reference.NAME);
+			String bind = method;
+			String unbind = null;
+
+			if (name == null) {
+				Matcher m = BINDMETHOD.matcher(method);
+				if (m.matches()) {
+					name = m.group(2).toLowerCase() + m.group(3);
+				} else {
+					name = method.toLowerCase();
+				}
+			}
+			String simpleName = name;
+
+			unbind = annotation.get(Reference.UNBIND);
+
+			if (bind != null) {
+				name = name + "/" + bind;
+				if (unbind != null)
+					name = name + "/" + unbind;
+			}
+			String service = annotation.get(Reference.SERVICE);
+
+			if (service != null) {
+				service = Clazz.objectDescriptorToFQN(service);
+			} else {
+				// We have to find the type of the current method to
+				// link it to the referenced service.
+				Matcher m = BINDDESCRIPTOR.matcher(methodDescriptor);
+				if (m.matches()) {
+					service = Clazz.internalToFqn(m.group(1));
+				} else
+					throw new IllegalArgumentException(
+							"Cannot detect the type of a Component Reference from the descriptor: "
+									+ methodDescriptor);
+			}
+
+			// Check if we have a target, this must be a filter
+			String target = annotation.get(Reference.TARGET);
+			if (target != null) {
+				Verifier.verifyFilter(target, 0);
+				service = service + target;
+			}
+
+			Integer c = annotation.get(Reference.TYPE);
+			if (c != null && !c.equals(0) && !c.equals((int) '1')) {
+				service = service + (char) c.intValue();
+			}
+
+			if (map.containsKey(name))
+				reporter.error(
+						"In component %s, Multiple references with the same name: %s. Previous def: %s, this def: %s",
+						name, map.get(name), service, "");
+			map.put(name, service);
+
+			if (isTrue(annotation.get(Reference.MULTIPLE)))
+				multiple.add(simpleName);
+			if (isTrue(annotation.get(Reference.OPTIONAL)))
+				optional.add(simpleName);
+			if (isTrue(annotation.get(Reference.DYNAMIC)))
+				dynamic.add(simpleName);
+
+			if (!checkMethod())
+				setVersion(V1_1);
+			else if (REFERENCEBINDDESCRIPTOR.matcher(methodDescriptor).matches()
+					|| !OLDBINDDESCRIPTOR.matcher(methodDescriptor).matches())
+				setVersion(V1_1);
+		}
+	}
+
+	private void setVersion(String v) {
+		if (v == null)
+			return;
+
+		if (version == null)
+			version = v;
+		else if (v.compareTo(version) > 0) // we're safe to 9.9.9
+			version = v;
+	}
+
+	private boolean checkMethod() {
+		return Modifier.isPublic(methodAccess) || Modifier.isProtected(methodAccess);
+	}
+
+	static Pattern	PROPERTY_PATTERN	= Pattern.compile("[^=]+=.+");
+
+	private void doProperties(Annotation annotation) {
+		Object[] properties = annotation.get(Component.PROPERTIES);
+
+		if (properties != null) {
+			for (Object o : properties) {
+				String p = (String) o;
+				if (PROPERTY_PATTERN.matcher(p).matches())
+					this.properties.add(p);
+				else
+					throw new IllegalArgumentException("Malformed property '" + p + "' on: "
+							+ annotation.get(Component.NAME));
+			}
+		}
+	}
+
+	private boolean isTrue(Object object) {
+		if (object == null)
+			return false;
+		return (Boolean) object;
+	}
+
+	private void setBoolean(String string, Object object, boolean b) {
+		if (object == null)
+			object = b;
+
+		Boolean bb = (Boolean) object;
+		if (bb == b)
+			return;
+
+		map.put(string, bb.toString());
+	}
+
+	private void set(String string, Object object, Object deflt) {
+		if (object == null || object.equals(deflt))
+			return;
+
+		map.put(string, object.toString());
+	}
+
+	/**
+	 * Skip L and ; and replace / for . in an object descriptor.
+	 * 
+	 * A string like Lcom/acme/Foo; becomes com.acme.Foo
+	 * 
+	 * @param string
+	 * @return
+	 */
+
+	private String descriptorToFQN(String string) {
+		StringBuilder sb = new StringBuilder();
+		for (int i = 1; i < string.length() - 1; i++) {
+			char c = string.charAt(i);
+			if (c == '/')
+				c = '.';
+			sb.append(c);
+		}
+		return sb.toString();
+	}
+
+	@Override public void classBegin(int access, String name) {
+		className = name;
+	}
+
+	@Override public void implementsInterfaces(String[] interfaces) {
+		this.interfaces = interfaces;
+	}
+
+	@Override public void method(int access, String name, String descriptor) {
+		this.method = name;
+		this.methodDescriptor = descriptor;
+		this.methodAccess = access;
+		descriptors.add(method);
+	}
+
+	void set(String name, Collection<String> l) {
+		if (l.size() == 0)
+			return;
+
+		set(name, Processor.join(l), "<>");
+	}
+
+	public void finish() {
+		set(COMPONENT_MULTIPLE, multiple);
+		set(COMPONENT_DYNAMIC, dynamic);
+		set(COMPONENT_OPTIONAL, optional);
+		set(COMPONENT_IMPLEMENTATION, clazz.getFQN(), "<>");
+		set(COMPONENT_PROPERTIES, properties);
+		if (version != null) {
+			set(COMPONENT_VERSION, version, "<>");
+			reporter.trace("Component %s is v1.1", map);
+		}
+		set(COMPONENT_DESCRIPTORS, descriptors);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java b/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
new file mode 100644
index 0000000..652d367
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
@@ -0,0 +1,665 @@
+package aQute.bnd.make.component;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.component.*;
+import aQute.bnd.make.metatype.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+import aQute.libg.version.*;
+
+/**
+ * This class is an analyzer plugin. It looks at the properties and tries to
+ * find out if the Service-Component header contains the bnd shortut syntax. If
+ * not, the header is copied to the output, if it does, an XML file is created
+ * and added to the JAR and the header is modified appropriately.
+ */
+public class ServiceComponent implements AnalyzerPlugin {
+	public final static String			NAMESPACE_STEM					= "http://www.osgi.org/xmlns/scr";
+	public final static String			JIDENTIFIER						= "<<identifier>>";
+	public final static String			COMPONENT_NAME					= "name:";
+	public final static String			COMPONENT_FACTORY				= "factory:";
+	public final static String			COMPONENT_SERVICEFACTORY		= "servicefactory:";
+	public final static String			COMPONENT_IMMEDIATE				= "immediate:";
+	public final static String			COMPONENT_ENABLED				= "enabled:";
+	public final static String			COMPONENT_DYNAMIC				= "dynamic:";
+	public final static String			COMPONENT_MULTIPLE				= "multiple:";
+	public final static String			COMPONENT_PROVIDE				= "provide:";
+	public final static String			COMPONENT_OPTIONAL				= "optional:";
+	public final static String			COMPONENT_PROPERTIES			= "properties:";
+	public final static String			COMPONENT_IMPLEMENTATION		= "implementation:";
+	public final static String			COMPONENT_DESIGNATE				= "designate:";
+	public final static String			COMPONENT_DESIGNATEFACTORY		= "designateFactory:";
+	public final static String			COMPONENT_DESCRIPTORS			= ".descriptors:";
+
+	// v1.1.0
+	public final static String			COMPONENT_VERSION				= "version:";
+	public final static String			COMPONENT_CONFIGURATION_POLICY	= "configuration-policy:";
+	public final static String			COMPONENT_MODIFIED				= "modified:";
+	public final static String			COMPONENT_ACTIVATE				= "activate:";
+	public final static String			COMPONENT_DEACTIVATE			= "deactivate:";
+
+	final static Map<String, String>	EMPTY							= Collections.emptyMap();
+
+	public final static String[]		componentDirectives				= new String[] {
+			COMPONENT_FACTORY, COMPONENT_IMMEDIATE, COMPONENT_ENABLED, COMPONENT_DYNAMIC,
+			COMPONENT_MULTIPLE, COMPONENT_PROVIDE, COMPONENT_OPTIONAL, COMPONENT_PROPERTIES,
+			COMPONENT_IMPLEMENTATION, COMPONENT_SERVICEFACTORY, COMPONENT_VERSION,
+			COMPONENT_CONFIGURATION_POLICY, COMPONENT_MODIFIED, COMPONENT_ACTIVATE,
+			COMPONENT_DEACTIVATE, COMPONENT_NAME, COMPONENT_DESCRIPTORS, COMPONENT_DESIGNATE,
+			COMPONENT_DESIGNATEFACTORY									};
+
+	public final static Set<String>		SET_COMPONENT_DIRECTIVES		= new HashSet<String>(
+																				Arrays.asList(componentDirectives));
+
+	public final static Set<String>		SET_COMPONENT_DIRECTIVES_1_1	= //
+																		new HashSet<String>(
+																				Arrays.asList(
+																						COMPONENT_VERSION,
+																						COMPONENT_CONFIGURATION_POLICY,
+																						COMPONENT_MODIFIED,
+																						COMPONENT_ACTIVATE,
+																						COMPONENT_DEACTIVATE));
+
+	public boolean analyzeJar(Analyzer analyzer) throws Exception {
+
+		ComponentMaker m = new ComponentMaker(analyzer);
+
+		Map<String, Map<String, String>> l = m.doServiceComponent();
+
+		analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor.printClauses(l));
+
+		analyzer.getInfo(m, "Service-Component: ");
+		m.close();
+
+		return false;
+	}
+
+	private static class ComponentMaker extends Processor {
+		Analyzer	analyzer;
+
+		ComponentMaker(Analyzer analyzer) {
+			super(analyzer);
+			this.analyzer = analyzer;
+		}
+
+		/**
+		 * Iterate over the Service Component entries. There are two cases:
+		 * <ol>
+		 * <li>An XML file reference</li>
+		 * <li>A FQN/wildcard with a set of attributes</li>
+		 * </ol>
+		 * 
+		 * An XML reference is immediately expanded, an FQN/wildcard is more
+		 * complicated and is delegated to
+		 * {@link #componentEntry(Map, String, Map)}.
+		 * 
+		 * @throws Exception
+		 */
+		Map<String, Map<String, String>> doServiceComponent() throws Exception {
+			Map<String, Map<String, String>> serviceComponents = newMap();
+			String header = getProperty(SERVICE_COMPONENT);
+			Map<String, Map<String, String>> sc = parseHeader(header);
+
+			for (Map.Entry<String, Map<String, String>> entry : sc.entrySet()) {
+				String name = entry.getKey();
+				Map<String, String> info = entry.getValue();
+
+				try {
+					if (name.indexOf('/') >= 0 || name.endsWith(".xml")) {
+						// Normal service component, we do not process it
+						serviceComponents.put(name, EMPTY);
+					} else {
+						componentEntry(serviceComponents, name, info);
+					}
+				} catch (Exception e) {
+					e.printStackTrace();
+					error("Invalid Service-Component header: %s %s, throws %s", name, info, e);
+				}
+			}
+			return serviceComponents;
+		}
+
+		/**
+		 * Parse an entry in the Service-Component header. This header supports
+		 * the following types:
+		 * <ol>
+		 * <li>An FQN + attributes describing a component</li>
+		 * <li>A wildcard expression for finding annotated components.</li>
+		 * </ol>
+		 * The problem is the distinction between an FQN and a wildcard because
+		 * an FQN can also be used as a wildcard.
+		 * 
+		 * If the info specifies {@link Constants#NOANNOTATIONS} then wildcards
+		 * are an error and the component must be fully described by the info.
+		 * Otherwise the FQN/wildcard is expanded into a list of classes with
+		 * annotations. If this list is empty, the FQN case is interpreted as a
+		 * complete component definition. For the wildcard case, it is checked
+		 * if any matching classes for the wildcard have been compiled for a
+		 * class file format that does not support annotations, this can be a
+		 * problem with JSR14 who silently ignores annotations. An error is
+		 * reported in such a case.
+		 * 
+		 * @param serviceComponents
+		 * @param name
+		 * @param info
+		 * @throws Exception
+		 * @throws IOException
+		 */
+		private void componentEntry(Map<String, Map<String, String>> serviceComponents,
+				String name, Map<String, String> info) throws Exception, IOException {
+
+			boolean annotations = !Processor.isTrue(info.get(NOANNOTATIONS));
+			boolean fqn = Verifier.isFQN(name);
+
+			if (annotations) {
+
+				// Annotations possible!
+
+				Collection<Clazz> annotatedComponents = analyzer.getClasses("",
+						QUERY.ANNOTATION.toString(), Component.class.getName(), //
+						QUERY.NAMED.toString(), name //
+						);
+
+				if (fqn) {
+					if (annotatedComponents.isEmpty()) {
+
+						// No annotations, fully specified in header
+
+						createComponentResource(serviceComponents, name, info);
+					} else {
+
+						// We had a FQN so expect only one
+
+						for (Clazz c : annotatedComponents) {
+							annotated(serviceComponents, c, info);
+						}
+					}
+				} else {
+
+					// We did not have an FQN, so expect the use of wildcards
+
+					if (annotatedComponents.isEmpty())
+						checkAnnotationsFeasible(name);
+					else
+						for (Clazz c : annotatedComponents) {
+							annotated(serviceComponents, c, info);
+						}
+				}
+			} else {
+				// No annotations
+				if (fqn)
+					createComponentResource(serviceComponents, name, info);
+				else
+					error("Set to %s but entry %s is not an FQN ", NOANNOTATIONS, name);
+
+			}
+		}
+
+		/**
+		 * Check if annotations are actually feasible looking at the class
+		 * format. If the class format does not provide annotations then it is
+		 * no use specifying annotated components.
+		 * 
+		 * @param name
+		 * @return
+		 * @throws Exception
+		 */
+		private Collection<Clazz> checkAnnotationsFeasible(String name) throws Exception {
+			Collection<Clazz> not = analyzer.getClasses("", QUERY.NAMED.toString(), name //
+					);
+			
+			if (not.isEmpty())
+				if ( "*".equals(name))
+					return not;
+				else
+					error("Specified %s but could not find any class matching this pattern", name);
+
+			for (Clazz c : not) {
+				if (c.getFormat().hasAnnotations())
+					return not;
+			}
+			
+			warning("Wildcards are used (%s) requiring annotations to decide what is a component. Wildcard maps to classes that are compiled with java.target < 1.5. Annotations were introduced in Java 1.5",
+					name);
+			
+			return not;
+		}
+
+		void annotated(Map<String, Map<String, String>> components, Clazz c,
+				Map<String, String> info) throws Exception {
+			// Get the component definition
+			// from the annotations
+			Map<String, String> map = ComponentAnnotationReader.getDefinition(c, this);
+
+			// Pick the name, the annotation can override
+			// the name.
+			String localname = map.get(COMPONENT_NAME);
+			if (localname == null)
+				localname = c.getFQN();
+
+			// Override the component info without manifest
+			// entries. We merge the properties though.
+
+			String merged = Processor.merge(info.remove(COMPONENT_PROPERTIES),
+					map.remove(COMPONENT_PROPERTIES));
+			if (merged != null && merged.length() > 0)
+				map.put(COMPONENT_PROPERTIES, merged);
+			map.putAll(info);
+			createComponentResource(components, localname, map);
+		}
+
+		private void createComponentResource(Map<String, Map<String, String>> components,
+				String name, Map<String, String> info) throws IOException {
+
+			// We can override the name in the parameters
+			if (info.containsKey(COMPONENT_NAME))
+				name = info.get(COMPONENT_NAME);
+
+			// Assume the impl==name, but allow override
+			String impl = name;
+			if (info.containsKey(COMPONENT_IMPLEMENTATION))
+				impl = info.get(COMPONENT_IMPLEMENTATION);
+
+			// Check if such a class exists
+			analyzer.referTo(impl);
+
+			boolean designate = designate(name, info.get(COMPONENT_DESIGNATE), false)
+					|| designate(name, info.get(COMPONENT_DESIGNATEFACTORY), true);
+
+			// If we had a designate, we want a default configuration policy of
+			// require.
+			if (designate && info.get(COMPONENT_CONFIGURATION_POLICY) == null)
+				info.put(COMPONENT_CONFIGURATION_POLICY, "require");
+
+			// We have a definition, so make an XML resources
+			Resource resource = createComponentResource(name, impl, info);
+			analyzer.getJar().putResource("OSGI-INF/" + name + ".xml", resource);
+
+			components.put("OSGI-INF/" + name + ".xml", EMPTY);
+
+		}
+
+		/**
+		 * Create a Metatype and Designate record out of the given
+		 * configurations.
+		 * 
+		 * @param name
+		 * @param config
+		 */
+		private boolean designate(String name, String config, boolean factory) {
+			if (config == null)
+				return false;
+
+			for (String c : Processor.split(config)) {
+				Clazz clazz = analyzer.getClassspace().get(Clazz.fqnToPath(c));
+				if (clazz != null) {
+					analyzer.referTo(c);
+					MetaTypeReader r = new MetaTypeReader(clazz, analyzer);
+					r.setDesignate(name, factory);
+					String rname = "OSGI-INF/metatype/" + name + ".xml";
+
+					analyzer.getJar().putResource(rname, r);
+				} else {
+					analyzer.error(
+							"Cannot find designated configuration class %s for component %s", c,
+							name);
+				}
+			}
+			return true;
+		}
+
+		/**
+		 * Create the resource for a DS component.
+		 * 
+		 * @param list
+		 * @param name
+		 * @param info
+		 * @throws UnsupportedEncodingException
+		 */
+		Resource createComponentResource(String name, String impl, Map<String, String> info)
+				throws IOException {
+			String namespace = getNamespace(info);
+			ByteArrayOutputStream out = new ByteArrayOutputStream();
+			PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, Constants.DEFAULT_CHARSET));
+			pw.println("<?xml version='1.0' encoding='utf-8'?>");
+			if (namespace != null)
+				pw.print("<scr:component xmlns:scr='" + namespace + "'");
+			else
+				pw.print("<component");
+
+			doAttribute(pw, name, "name");
+			doAttribute(pw, info.get(COMPONENT_FACTORY), "factory");
+			doAttribute(pw, info.get(COMPONENT_IMMEDIATE), "immediate", "false", "true");
+			doAttribute(pw, info.get(COMPONENT_ENABLED), "enabled", "true", "false");
+			doAttribute(pw, info.get(COMPONENT_CONFIGURATION_POLICY), "configuration-policy",
+					"optional", "require", "ignore");
+			doAttribute(pw, info.get(COMPONENT_ACTIVATE), "activate", JIDENTIFIER);
+			doAttribute(pw, info.get(COMPONENT_DEACTIVATE), "deactivate", JIDENTIFIER);
+			doAttribute(pw, info.get(COMPONENT_MODIFIED), "modified", JIDENTIFIER);
+
+			pw.println(">");
+
+			// Allow override of the implementation when people
+			// want to choose their own name
+			pw.println("  <implementation class='" + (impl == null ? name : impl) + "'/>");
+
+			String provides = info.get(COMPONENT_PROVIDE);
+			boolean servicefactory = Processor.isTrue(info.get(COMPONENT_SERVICEFACTORY));
+
+			if (servicefactory && Processor.isTrue(info.get(COMPONENT_IMMEDIATE))) {
+				// TODO can become error() if it is up to me
+				warning("For a Service Component, the immediate option and the servicefactory option are mutually exclusive for %(%s)",
+						name, impl);
+			}
+			provide(pw, provides, servicefactory, impl);
+			properties(pw, info);
+			reference(info, pw);
+
+			if (namespace != null)
+				pw.println("</scr:component>");
+			else
+				pw.println("</component>");
+
+			pw.close();
+			byte[] data = out.toByteArray();
+			out.close();
+			return new EmbeddedResource(data, 0);
+		}
+
+		private void doAttribute(PrintWriter pw, String value, String name, String... matches) {
+			if (value != null) {
+				if (matches.length != 0) {
+					if (matches.length == 1 && matches[0].equals(JIDENTIFIER)) {
+						if (!Verifier.isIdentifier(value))
+							error("Component attribute %s has value %s but is not a Java identifier",
+									name, value);
+					} else {
+
+						if (!Verifier.isMember(value, matches))
+							error("Component attribute %s has value %s but is not a member of %s",
+									name, value, Arrays.toString(matches));
+					}
+				}
+				pw.print(" ");
+				pw.print(name);
+				pw.print("='");
+				pw.print(value);
+				pw.print("'");
+			}
+		}
+
+		/**
+		 * Check if we need to use the v1.1 namespace (or later).
+		 * 
+		 * @param info
+		 * @return
+		 */
+		private String getNamespace(Map<String, String> info) {
+			String version = info.get(COMPONENT_VERSION);
+			if (version != null) {
+				try {
+					Version v = new Version(version);
+					return NAMESPACE_STEM + "/v" + v;
+				} catch (Exception e) {
+					error("version: specified on component header but not a valid version: "
+							+ version);
+					return null;
+				}
+			}
+			for (String key : info.keySet()) {
+				if (SET_COMPONENT_DIRECTIVES_1_1.contains(key)) {
+					return NAMESPACE_STEM + "/v1.1.0";
+				}
+			}
+			return null;
+		}
+
+		/**
+		 * Print the Service-Component properties element
+		 * 
+		 * @param pw
+		 * @param info
+		 */
+		void properties(PrintWriter pw, Map<String, String> info) {
+			Collection<String> properties = split(info.get(COMPONENT_PROPERTIES));
+			for (Iterator<String> p = properties.iterator(); p.hasNext();) {
+				String clause = p.next();
+				int n = clause.indexOf('=');
+				if (n <= 0) {
+					error("Not a valid property in service component: " + clause);
+				} else {
+					String type = null;
+					String name = clause.substring(0, n);
+					if (name.indexOf('@') >= 0) {
+						String parts[] = name.split("@");
+						name = parts[1];
+						type = parts[0];
+					} else if (name.indexOf(':') >= 0) {
+						String parts[] = name.split(":");
+						name = parts[0];
+						type = parts[1];
+					}
+					String value = clause.substring(n + 1).trim();
+					// TODO verify validity of name and value.
+					pw.print("  <property name='");
+					pw.print(name);
+					pw.print("'");
+
+					if (type != null) {
+						if (VALID_PROPERTY_TYPES.matcher(type).matches()) {
+							pw.print(" type='");
+							pw.print(type);
+							pw.print("'");
+						} else {
+							warning("Invalid property type '" + type + "' for property " + name);
+						}
+					}
+
+					String parts[] = value.split("\\s*(\\||\\n)\\s*");
+					if (parts.length > 1) {
+						pw.println(">");
+						for (String part : parts) {
+							pw.println(part);
+						}
+						pw.println("</property>");
+					} else {
+						pw.print(" value='");
+						pw.print(parts[0]);
+						pw.println("'/>");
+					}
+				}
+			}
+		}
+
+		/**
+		 * @param pw
+		 * @param provides
+		 */
+		void provide(PrintWriter pw, String provides, boolean servicefactory, String impl) {
+			if (provides != null) {
+				if (!servicefactory)
+					pw.println("  <service>");
+				else
+					pw.println("  <service servicefactory='true'>");
+
+				StringTokenizer st = new StringTokenizer(provides, ",");
+				while (st.hasMoreTokens()) {
+					String interfaceName = st.nextToken();
+					pw.println("    <provide interface='" + interfaceName + "'/>");
+					analyzer.referTo(interfaceName);
+
+					// TODO verifies the impl. class extends or implements the
+					// interface
+				}
+				pw.println("  </service>");
+			} else if (servicefactory)
+				warning("The servicefactory:=true directive is set but no service is provided, ignoring it");
+		}
+
+		public final static Pattern	REFERENCE	= Pattern.compile("([^(]+)(\\(.+\\))?");
+
+		/**
+		 * @param info
+		 * @param pw
+		 */
+
+		void reference(Map<String, String> info, PrintWriter pw) {
+			Collection<String> dynamic = new ArrayList<String>(split(info.get(COMPONENT_DYNAMIC)));
+			Collection<String> optional = new ArrayList<String>(split(info.get(COMPONENT_OPTIONAL)));
+			Collection<String> multiple = new ArrayList<String>(split(info.get(COMPONENT_MULTIPLE)));
+
+			Collection<String> descriptors = split(info.get(COMPONENT_DESCRIPTORS));
+
+			for (Map.Entry<String, String> entry : info.entrySet()) {
+
+				// Skip directives
+				String referenceName = entry.getKey();
+				if (referenceName.endsWith(":")) {
+					if (!SET_COMPONENT_DIRECTIVES.contains(referenceName))
+						error("Unrecognized directive in Service-Component header: "
+								+ referenceName);
+					continue;
+				}
+
+				// Parse the bind/unbind methods from the name
+				// if set. They are separated by '/'
+				String bind = null;
+				String unbind = null;
+
+				boolean unbindCalculated = false;
+
+				if (referenceName.indexOf('/') >= 0) {
+					String parts[] = referenceName.split("/");
+					referenceName = parts[0];
+					bind = parts[1];
+					if (parts.length > 2) {
+						unbind = parts[2];
+					} else {
+						unbindCalculated = true;
+						if (bind.startsWith("add"))
+							unbind = bind.replaceAll("add(.+)", "remove$1");
+						else
+							unbind = "un" + bind;
+					}
+				} else if (Character.isLowerCase(referenceName.charAt(0))) {
+					unbindCalculated = true;
+					bind = "set" + Character.toUpperCase(referenceName.charAt(0))
+							+ referenceName.substring(1);
+					unbind = "un" + bind;
+				}
+
+				String interfaceName = entry.getValue();
+				if (interfaceName == null || interfaceName.length() == 0) {
+					error("Invalid Interface Name for references in Service Component: "
+							+ referenceName + "=" + interfaceName);
+					continue;
+				}
+
+				// If we have descriptors, we have analyzed the component.
+				// So why not check the methods
+				if (descriptors.size() > 0) {
+					// Verify that the bind method exists
+					if (!descriptors.contains(bind))
+						error("The bind method %s for %s not defined", bind, referenceName);
+
+					// Check if the unbind method exists
+					if (!descriptors.contains(unbind)) {
+						if (unbindCalculated)
+							// remove it
+							unbind = null;
+						else
+							error("The unbind method %s for %s not defined", unbind, referenceName);
+					}
+				}
+				// Check tje cardinality by looking at the last
+				// character of the value
+				char c = interfaceName.charAt(interfaceName.length() - 1);
+				if ("?+*~".indexOf(c) >= 0) {
+					if (c == '?' || c == '*' || c == '~')
+						optional.add(referenceName);
+					if (c == '+' || c == '*')
+						multiple.add(referenceName);
+					if (c == '+' || c == '*' || c == '?')
+						dynamic.add(referenceName);
+					interfaceName = interfaceName.substring(0, interfaceName.length() - 1);
+				}
+
+				// Parse the target from the interface name
+				// The target is a filter.
+				String target = null;
+				Matcher m = REFERENCE.matcher(interfaceName);
+				if (m.matches()) {
+					interfaceName = m.group(1);
+					target = m.group(2);
+				}
+
+				analyzer.referTo(interfaceName);
+
+				pw.printf("  <reference name='%s'", referenceName);
+				pw.printf(" interface='%s'", interfaceName);
+
+				String cardinality = optional.contains(referenceName) ? "0" : "1";
+				cardinality += "..";
+				cardinality += multiple.contains(referenceName) ? "n" : "1";
+				if (!cardinality.equals("1..1"))
+					pw.print(" cardinality='" + cardinality + "'");
+
+				if (bind != null) {
+					pw.printf(" bind='%s'", bind);
+					if (unbind != null) {
+						pw.printf(" unbind='%s'", unbind);
+					}
+				}
+
+				if (dynamic.contains(referenceName)) {
+					pw.print(" policy='dynamic'");
+				}
+
+				if (target != null) {
+					// Filter filter = new Filter(target);
+					// if (filter.verify() == null)
+					// pw.print(" target='" + filter.toString() + "'");
+					// else
+					// error("Target for " + referenceName
+					// + " is not a correct filter: " + target + " "
+					// + filter.verify());
+					pw.print(" target='" + escape(target) + "'");
+				}
+				pw.println("/>");
+			}
+		}
+	}
+
+	/**
+	 * Escape a string, do entity conversion.
+	 */
+	static String escape(String s) {
+		StringBuffer sb = new StringBuffer();
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			switch (c) {
+			case '<':
+				sb.append("&lt;");
+				break;
+			case '>':
+				sb.append("&gt;");
+				break;
+			case '&':
+				sb.append("&amp;");
+				break;
+			case '\'':
+				sb.append("&quot;");
+				break;
+			default:
+				sb.append(c);
+				break;
+			}
+		}
+		return sb.toString();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java b/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
new file mode 100644
index 0000000..bc33dc2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
@@ -0,0 +1,95 @@
+package aQute.bnd.make.coverage;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+
+/**
+ * This class can create a coverage table between two classspaces. The
+ * destination class space is used to create a table of methods. All source
+ * methods that refer to a specific dest are then filled into the table.
+ * 
+ */
+public class Coverage {
+
+    /**
+     * Create a cross reference table from source to dest.
+     * 
+     * @param source
+     *            The methods that refer to dest
+     * @param dest
+     *            The methods that are being referred to
+     * @return A mapping of source methods to destination methods.
+     * @throws IOException
+     */
+    public static Map<MethodDef, List<MethodDef>> getCrossRef(
+            Collection<Clazz> source, Collection<Clazz> dest)
+            throws Exception {
+        final Map<MethodDef, List<MethodDef>> catalog = buildCatalog(dest);
+        crossRef(source, catalog);
+        return catalog;
+    }
+
+    private static void crossRef(Collection<Clazz> source,
+            final Map<MethodDef, List<MethodDef>> catalog) throws Exception {
+        for (final Clazz clazz : source) {
+            clazz.parseClassFileWithCollector(new ClassDataCollector() {
+                MethodDef source;
+
+                public void implementsInterfaces(String names[]) {
+                    MethodDef def = new MethodDef(0, clazz.getFQN(),
+                            "<implements>", "()V");
+                    for (String interfaceName : names) {
+                        interfaceName = Clazz.internalToFqn(interfaceName);
+                        for (Map.Entry<MethodDef, List<MethodDef>> entry : catalog
+                                .entrySet()) {
+                            String catalogClass = entry.getKey().clazz;
+                            List<MethodDef> references = entry.getValue();
+
+                            if (catalogClass.equals(interfaceName)) {
+                                references.add(def);
+                            }
+                        }
+                    }
+                }
+
+                // Method definitions
+                public void method(MethodDef source) {
+                    this.source = source;
+                }
+
+                public void reference(MethodDef reference) {
+                    List<MethodDef> references = catalog.get(reference);
+                    if (references != null) {
+                        references.add(source);
+                    }
+                }
+            });
+        }
+    }
+
+    private static Map<MethodDef, List<MethodDef>> buildCatalog(
+            Collection<Clazz> sources) throws Exception {
+        final Map<MethodDef, List<MethodDef>> catalog = new TreeMap<MethodDef, List<MethodDef>>();
+        for (final Clazz clazz : sources) {
+            clazz.parseClassFileWithCollector(new ClassDataCollector() {
+
+                public boolean classStart(int access, String name) {
+                    return clazz.isPublic();
+                }
+
+                public void method(MethodDef source) {
+                    if (java.lang.reflect.Modifier.isPublic(source.access)
+                            || Modifier.isProtected(source.access))
+                        catalog.put(source, new ArrayList<MethodDef>());
+                }
+
+            });
+        }
+        return catalog;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/coverage/CoverageResource.java b/bundleplugin/src/main/java/aQute/bnd/make/coverage/CoverageResource.java
new file mode 100644
index 0000000..3827391
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/coverage/CoverageResource.java
@@ -0,0 +1,92 @@
+package aQute.bnd.make.coverage;
+
+import static aQute.bnd.make.coverage.Coverage.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+import aQute.lib.tag.*;
+
+/**
+ * Creates an XML Coverage report. This class can be used as a resource so the
+ * report is created only when the JAR is written.
+ * 
+ */
+public class CoverageResource extends WriteResource {
+    Collection<Clazz> testsuite;
+    Collection<Clazz> service;
+
+    public CoverageResource(Collection<Clazz> testsuite,
+            Collection<Clazz> service) {
+        this.testsuite = testsuite;
+        this.service = service;
+    }
+
+    @Override
+    public long lastModified() {
+        return 0;
+    }
+
+    @Override
+    public void write(OutputStream out) throws IOException {
+        try {
+            Map<MethodDef, List<MethodDef>> table = getCrossRef(testsuite,
+                    service);
+            Tag coverage = toTag(table);
+            PrintWriter pw = new PrintWriter(new OutputStreamWriter(out,
+                    Constants.DEFAULT_CHARSET));
+            try {
+                coverage.print(0, pw);
+            } finally {
+                pw.flush();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static Tag toTag(Map<MethodDef, List<MethodDef>> catalog) {
+        Tag coverage = new Tag("coverage");
+        String currentClass = null;
+        Tag classTag = null;
+
+        for (Map.Entry<MethodDef, List<MethodDef>> m : catalog.entrySet()) {
+            String className = m.getKey().clazz;
+            if (!className.equals(currentClass)) {
+                classTag = new Tag("class");
+                classTag.addAttribute("name", className);
+                classTag.addAttribute("package", Clazz.getPackage(className));
+                classTag.addAttribute("short", Clazz.getShortName(className));
+                coverage.addContent(classTag);
+                currentClass = className;
+            }
+            Tag method = doMethod(new Tag("method"), m.getKey());
+            classTag.addContent(method);
+            for (MethodDef r : m.getValue()) {
+                Tag ref = doMethod(new Tag("ref"), r);
+                method.addContent(ref);
+            }
+        }
+        return coverage;
+    }
+
+    private static Tag doMethod(Tag tag, MethodDef method) {
+        tag.addAttribute("pretty", method.getPretty());
+        if (Modifier.isPublic(method.access))
+            tag.addAttribute("public", true);
+        if (Modifier.isStatic(method.access))
+            tag.addAttribute("static", true);
+        if (Modifier.isProtected(method.access))
+            tag.addAttribute("protected", true);
+        if (Modifier.isInterface(method.access))
+            tag.addAttribute("interface", true);
+        tag.addAttribute("constructor", method.isConstructor());
+        if (!method.isConstructor())
+            tag.addAttribute("name", method.name);
+        tag.addAttribute("descriptor", method.descriptor);
+        return tag;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetaTypeReader.java b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetaTypeReader.java
new file mode 100644
index 0000000..94a65ae
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetaTypeReader.java
@@ -0,0 +1,341 @@
+package aQute.bnd.make.metatype;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.metatype.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.tag.*;
+import aQute.libg.generics.*;
+
+public class MetaTypeReader extends ClassDataCollector implements Resource {
+	final Analyzer				reporter;
+	Clazz						clazz;
+	String						interfaces[];
+	Tag							metadata	= new Tag("metatype:MetaData", new String[] {
+			"xmlns:metatype", "http://www.osgi.org/xmlns/metatype/v1.1.0" });
+	Tag							ocd			= new Tag(metadata, "OCD");
+	Tag							designate	= new Tag(metadata, "Designate");
+	Tag							object		= new Tag(designate, "Object");
+
+	// Resource
+	String						extra;
+
+	// One time init
+	boolean						finished;
+
+	// Designate
+	boolean						override;
+	String						designatePid;
+	boolean						factory;
+
+	// AD
+	Map<MethodDef, Meta.AD>	methods		= new LinkedHashMap<MethodDef, Meta.AD>();
+
+	// OCD
+	Annotation					ocdAnnotation;
+
+	MethodDef					method;
+
+	public MetaTypeReader(Clazz clazz, Analyzer reporter) {
+		this.clazz = clazz;
+		this.reporter = reporter;
+	}
+
+	public void annotation(Annotation annotation) {
+		try {
+			Meta.OCD ocd = annotation.getAnnotation(Meta.OCD.class);
+			Meta.AD ad = annotation.getAnnotation(Meta.AD.class);
+			if (ocd != null) {
+				this.ocdAnnotation = annotation;
+			}
+			if (ad != null) {
+				assert method != null;
+				methods.put(method, ad);
+			}
+		} catch (Exception e) {
+			reporter.error("Error during annotation parsing %s : %s", clazz, e);
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @param id
+	 * @param name
+	 * @param cardinality
+	 * @param required
+	 * @param deflt
+	 * @param type
+	 * @param max
+	 * @param min
+	 * @param optionLabels
+	 * @param optionValues
+	 */
+
+	static Pattern	COLLECTION	= Pattern
+										.compile("(.*(Collection|Set|List|Queue|Stack|Deque))<(L.+;)>");
+
+	private void addMethod(MethodDef method, Meta.AD ad) throws Exception {
+
+		// Set all the defaults.
+		String rtype = method.getReturnType();
+		String id = Configurable.mangleMethodName(method.name);
+		String name = Clazz.unCamel(id);
+
+		int cardinality = 0;
+
+		if (rtype.endsWith("[]")) {
+			cardinality = Integer.MAX_VALUE;
+			rtype = rtype.substring(0, rtype.length() - 2);
+		}
+		if (rtype.indexOf('<') > 0) {
+			if (cardinality != 0)
+				reporter.error(
+						"AD for %s.%s uses an array of collections in return type (%s), Metatype allows either Vector or array",
+						clazz.getFQN(), method.name, method.getReturnType());
+			Matcher m = COLLECTION.matcher(rtype);
+			if (m.matches()) {
+				rtype = Clazz.objectDescriptorToFQN(m.group(3));
+				cardinality = Integer.MIN_VALUE;
+			}
+		}
+
+		Meta.Type type = getType(rtype);
+		
+		boolean required = ad ==null || ad.required();
+		String deflt = null;
+		String max = null;
+		String min = null;
+		String[] optionLabels = null;
+		String[] optionValues = null;
+		String description = null;
+
+		Clazz c = reporter.findClass(Clazz.fqnToPath(rtype));
+		if (c != null && c.isEnum()) {
+			optionValues = parseOptionValues(c);
+		}
+
+		// Now parse the annotation for any overrides
+
+		if (ad != null) {
+			if (ad.id() != null)
+				id = ad.id();
+			if (ad.name() != null)
+				name = ad.name();
+			if (ad.cardinality() != 0)
+				cardinality = ad.cardinality();
+			if (ad.type() != null)
+				type = ad.type();
+			if (ad.required() || ad.deflt() == null)
+				required = true;
+
+			if (ad.description() != null)
+				description = ad.description();
+
+			if (ad.optionLabels() != null)
+				optionLabels = ad.optionLabels();
+			if (ad.optionValues() != null )
+				optionValues = ad.optionValues();
+
+			if (ad.min() != null)
+				min = ad.min();
+			if (ad.max() != null)
+				max = ad.max();
+
+			if (ad.deflt() != null)
+				deflt = ad.deflt();
+		}
+
+		if (optionValues != null) {
+			if (optionLabels == null || optionLabels.length == 0) {
+				optionLabels = new String[optionValues.length];
+				for (int i = 0; i < optionValues.length; i++)
+					optionLabels[i] = Clazz.unCamel(optionValues[i]);
+			}
+
+			if (optionLabels.length != optionValues.length) {
+				reporter.error("Option labels and option values not the same length for %s", id);
+				optionLabels = optionValues;
+			}
+		}
+
+		Tag adt = new Tag(this.ocd, "AD");
+		adt.addAttribute("name", name);
+		adt.addAttribute("id", id);
+		adt.addAttribute("cardinality", cardinality);
+		adt.addAttribute("required", required);
+		adt.addAttribute("default", deflt);
+		adt.addAttribute("type", type);
+		adt.addAttribute("max", max);
+		adt.addAttribute("min", min);
+		adt.addAttribute("description", description);
+
+		if (optionLabels != null) {
+			for (int i = 0; i < optionLabels.length; i++) {
+				Tag option = new Tag(adt, "Option");
+				option.addAttribute("label", optionLabels[i]);
+				option.addAttribute("value", optionValues[i]);
+			}
+		}
+	}
+
+	private String[] parseOptionValues(Clazz c) throws Exception {
+		final List<String> values = Create.list();
+
+		c.parseClassFileWithCollector(new ClassDataCollector() {
+			public void field(Clazz.FieldDef def) {
+				if (def.isEnum()) {
+					values.add(def.name);
+				}
+			}
+		});
+		return values.toArray(new String[values.size()]);
+	}
+
+	Meta.Type getType(String rtype) {
+		if (rtype.endsWith("[]")) {
+			rtype = rtype.substring(0, rtype.length() - 2);
+			if (rtype.endsWith("[]"))
+				throw new IllegalArgumentException("Can only handle array of depth one");
+		}
+
+		if ("boolean".equals(rtype) || Boolean.class.getName().equals(rtype))
+			return Meta.Type.Boolean;
+		else if ("byte".equals(rtype) || Byte.class.getName().equals(rtype))
+			return Meta.Type.Byte;
+		else if ("char".equals(rtype) || Character.class.getName().equals(rtype))
+			return Meta.Type.Character;
+		else if ("short".equals(rtype) || Short.class.getName().equals(rtype))
+			return Meta.Type.Short;
+		else if ("int".equals(rtype) || Integer.class.getName().equals(rtype))
+			return Meta.Type.Integer;
+		else if ("long".equals(rtype) || Long.class.getName().equals(rtype))
+			return Meta.Type.Long;
+		else if ("float".equals(rtype) || Float.class.getName().equals(rtype))
+			return Meta.Type.Float;
+		else if ("double".equals(rtype) || Double.class.getName().equals(rtype))
+			return Meta.Type.Double;
+		else
+			return Meta.Type.String;
+	}
+
+	@Override public void method(MethodDef mdef) {
+		method = mdef;
+		methods.put(mdef, null);
+	}
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public long lastModified() {
+		return 0;
+	}
+
+	public InputStream openInputStream() throws IOException {
+		final PipedInputStream pin = new PipedInputStream();
+		final PipedOutputStream pout = new PipedOutputStream(pin);
+		getExecutor().execute(new Runnable() {
+			public void run() {
+				try {
+					write(pout);
+				} catch (IOException e) {
+					// Cause an exception in the other end
+					IO.close(pin);
+				}
+				IO.close(pout);
+			}
+		});
+		return pin;
+	}
+
+	private Executor getExecutor() {
+		return reporter.getPlugin(Executor.class);
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+
+	public void write(OutputStream out) throws IOException {
+		try {
+			finish();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+		PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
+		pw.println("<?xml version='1.0'?>");
+		metadata.print(0, pw);
+		pw.flush();
+	}
+
+	void finish() throws Exception {
+		if (!finished) {
+			finished = true;
+			clazz.parseClassFileWithCollector(this);
+			Meta.OCD ocd = null;
+			if (this.ocdAnnotation != null)
+				ocd = this.ocdAnnotation.getAnnotation(Meta.OCD.class);
+			else
+				ocd = Configurable.createConfigurable(Meta.OCD.class,
+						new HashMap<String, Object>());
+
+			// defaults
+			String id = clazz.getFQN();
+			String name = Clazz.unCamel(Clazz.getShortName(clazz.getFQN()));
+			String description = null;
+			String localization = id;
+			boolean factory = this.factory;
+
+			if (ocd.id() != null)
+				id = ocd.id();
+
+			
+			if (ocd.name() != null)
+				name = ocd.name();
+
+			if (ocd.localization() != null)
+				localization = ocd.localization();
+
+			if (ocd.description() != null)
+				description = ocd.description();
+
+			String pid = id;
+			if (override) {
+				pid = this.designatePid;
+				factory = this.factory;
+				id = this.designatePid; // for the felix problems
+			} else {
+				if (ocdAnnotation.get("factory") != null) {
+					factory = true;
+				}
+			}
+
+			this.ocd.addAttribute("name", name);
+			this.ocd.addAttribute("id", id);
+			this.ocd.addAttribute("description", description);
+			this.ocd.addAttribute("localization", localization);
+
+			// do ADs
+			for (Map.Entry<MethodDef, Meta.AD> entry : methods.entrySet())
+				addMethod(entry.getKey(), entry.getValue());
+
+			this.designate.addAttribute("pid", pid);
+			if (factory)
+				this.designate.addAttribute("factoryPid", pid);
+
+			this.object.addAttribute("ocdref", id);
+
+		}
+	}
+
+	public void setDesignate(String pid, boolean factory) {
+		this.override = true;
+		this.factory = factory;
+		this.designatePid = pid;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetatypePlugin.java b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetatypePlugin.java
new file mode 100644
index 0000000..4801ec9
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/make/metatype/MetatypePlugin.java
@@ -0,0 +1,35 @@
+package aQute.bnd.make.metatype;
+
+import java.util.*;
+
+import aQute.bnd.annotation.metatype.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.*;
+
+/**
+ * This class is responsible for meta type types. It is a plugin that can 
+ * @author aqute
+ *
+ */
+public class MetatypePlugin implements AnalyzerPlugin {
+
+	public boolean analyzeJar(Analyzer analyzer) throws Exception {
+
+		Map<String, Map<String, String>> map = analyzer.parseHeader(analyzer
+				.getProperty(Constants.METATYPE));
+
+		Jar jar = analyzer.getJar();
+		for (String name : map.keySet()) {
+			Collection<Clazz> metatypes = analyzer.getClasses("", QUERY.ANNOTATION.toString(),
+					Meta.OCD.class.getName(), // 
+					QUERY.NAMED.toString(), name //
+					);
+			for (Clazz c : metatypes) {
+				jar.putResource("OSGI-INF/metatype/" + c.getFQN() + ".xml", new MetaTypeReader(c,
+						analyzer));
+			}
+		}
+		return false;
+	}
+}