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/libg/asn1/BER.java b/bundleplugin/src/main/java/aQute/libg/asn1/BER.java
new file mode 100644
index 0000000..4416dd4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/asn1/BER.java
@@ -0,0 +1,472 @@
+package aQute.libg.asn1;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+
+public class BER implements Types {
+    DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss\\Z");
+    
+    final DataInputStream xin;
+    long                  position;
+
+    public BER(InputStream in) {
+        this.xin = new DataInputStream(in);
+    }
+
+    public void dump(PrintStream out) throws Exception {
+        int type = readByte();
+        long length = readLength();
+        if (type == -1 || length == -1)
+            throw new EOFException("Empty file");
+        dump(out, type, length, "");
+    }
+
+    void dump(PrintStream out, int type, long length, String indent)
+            throws Exception {
+        int clss = type >> 6;
+        int nmbr = type & 0x1F;
+        boolean cnst = (type & 0x20) != 0;
+
+        String tag = "[" + nmbr + "]";
+        if (clss == 0)
+            tag = TAGS[nmbr];
+
+        if (cnst) {
+            System.err.printf("%5d %s %s %s%n", length, indent, CLASSES[clss],
+                    tag);
+            while (length > 1) {
+                long atStart = getPosition();
+                int t2 = read();
+                long l2 = readLength();
+                dump(out, t2, l2, indent + "  ");
+                length -= getPosition() - atStart;
+            }
+        } else {
+            assert length < Integer.MAX_VALUE;
+            assert length >= 0;
+            byte[] data = new byte[(int) length];
+            readFully(data);
+            String summary;
+
+            switch (nmbr) {
+            case BOOLEAN:
+                assert length == 1;
+                summary = data[0] != 0 ? "true" : "false";
+                break;
+
+            case INTEGER:
+                long n = toLong(data);
+                summary = n + "";
+                break;
+
+            case UTF8_STRING:
+            case IA5STRING:
+            case VISIBLE_STRING:
+            case UNIVERSAL_STRING:
+            case PRINTABLE_STRING:
+            case UTCTIME:
+                summary = new String(data, "UTF-8");
+                break;
+
+            case OBJECT_IDENTIFIER:
+                summary = readOID(data);
+                break;
+
+            case GENERALIZED_TIME:
+            case GRAPHIC_STRING:
+            case GENERAL_STRING:
+            case CHARACTER_STRING:
+
+            case REAL:
+            case EOC:
+            case BIT_STRING:
+            case OCTET_STRING:
+            case NULL:
+            case OBJECT_DESCRIPTOR:
+            case EXTERNAL:
+            case ENUMERATED:
+            case EMBEDDED_PDV:
+            case RELATIVE_OID:
+            case NUMERIC_STRING:
+            case T61_STRING:
+            case VIDEOTEX_STRING:
+            case BMP_STRING:
+            default:
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < 10 && i < data.length; i++) {
+                    sb.append(Integer.toHexString(data[i]));
+                }
+                if (data.length > 10) {
+                    sb.append("...");
+                }
+                summary = sb.toString();
+                break;
+            }
+            out.printf("%5d %s %s %s %s\n", length, indent, CLASSES[clss], tag,
+                    summary);
+        }
+    }
+
+    long toLong(byte[] data) {
+        if (data[0] < 0) {
+            for (int i = 0; i < data.length; i++)
+                data[i] = (byte) (0xFF ^ data[i]);
+
+            return -(toLong(data) + 1);
+        }
+        long n = 0;
+        for (int i = 0; i < data.length; i++) {
+            n = n * 256 + data[i];
+        }
+        return n;
+    }
+
+    /**
+     * 8.1.3.3 For the definite form, the length octets shall consist of one or
+     * more octets, and shall represent the number of octets in the contents
+     * octets using either the short form (see 8.1.3.4) or the long form (see
+     * 8.1.3.5) as a sender's option. NOTE – The short form can only be used if
+     * the number of octets in the contents octets is less than or equal to 127.
+     * 8.1.3.4 In the short form, the length octets shall consist of a single
+     * octet in which bit 8 is zero and bits 7 to 1 encode the number of octets
+     * in the contents octets (which may be zero), as an unsigned binary integer
+     * with bit 7 as the most significant bit. EXAMPLE L = 38 can be encoded as
+     * 001001102 8.1.3.5 In the long form, the length octets shall consist of an
+     * initial octet and one or more subsequent octets. The initial octet shall
+     * be encoded as follows: a) bit 8 shall be one; b) bits 7 to 1 shall encode
+     * the number of subsequent octets in the length octets, as an unsigned
+     * binary integer with bit 7 as the most significant bit; c) the value
+     * 111111112 shall not be used. ISO/IEC 8825-1:2003 (E) NOTE 1 – This
+     * restriction is introduced for possible future extension. Bits 8 to 1 of
+     * the first subsequent octet, followed by bits 8 to 1 of the second
+     * subsequent octet, followed in turn by bits 8 to 1 of each further octet
+     * up to and including the last subsequent octet, shall be the encoding of
+     * an unsigned binary integer equal to the number of octets in the contents
+     * octets, with bit 8 of the first subsequent octet as the most significant
+     * bit. EXAMPLE L = 201 can be encoded as: 100000012 110010012 NOTE 2 – In
+     * the long form, it is a sender's option whether to use more length octets
+     * than the minimum necessary. 8.1.3.6 For the indefinite form, the length
+     * octets indicate that the contents octets are terminated by
+     * end-of-contents octets (see 8.1.5), and shall consist of a single octet.
+     * 8.1.3.6.1 The single octet shall have bit 8 set to one, and bits 7 to 1
+     * set to zero. 8.1.3.6.2 If this form of length is used, then
+     * end-of-contents octets (see 8.1.5) shall be present in the encoding
+     * following the contents octets. 8.1.4 Contents octets The contents octets
+     * shall consist of zero, one or more octets, and shall encode the data
+     * value as specified in subsequent clauses. NOTE – The contents octets
+     * depend on the type of the data value; subsequent clauses follow the same
+     * sequence as the definition of types in ASN.1. 8.1.5 End-of-contents
+     * octets The end-of-contents octets shall be present if the length is
+     * encoded as specified in 8.1.3.6, otherwise they shall not be present. The
+     * end-of-contents octets shall consist of two zero octets. NOTE – The
+     * end-of-contents octets can be considered as the encoding of a value whose
+     * tag is universal class, whose form is primitive, whose number of the tag
+     * is zero, and whose contents are absent, thus:
+     * 
+     * End-of-contents Length Contents 0016 0016 Absent
+     * 
+     * @return
+     */
+    private long readLength() throws IOException {
+        long n = readByte();
+        if (n > 0) {
+            // short form
+            return n;
+        }
+		// long form
+		int count = (int) (n & 0x7F);
+		if (count == 0) {
+		    // indefinite form
+		    return 0;
+		}
+		n = 0;
+		while (count-- > 0) {
+		    n = n * 256 + read();
+		}
+		return n;
+    }
+
+    private int readByte() throws IOException {
+        position++;
+        return xin.readByte();
+    }
+
+    private void readFully(byte[] data) throws IOException {
+        position += data.length;
+        xin.readFully(data);
+    }
+
+    private long getPosition() {
+        return position;
+    }
+
+    private int read() throws IOException {
+        position++;
+        return xin.read();
+    }
+
+    String readOID(byte[] data) {
+        StringBuilder sb = new StringBuilder();
+        sb.append((0xFF & data[0]) / 40);
+        sb.append(".");
+        sb.append((0xFF & data[0]) % 40);
+
+        int i = 0;
+        while (++i < data.length) {
+            int n = 0;
+            while (data[i] < 0) {
+                n = n * 128 + (0x7F & data[i]);
+                i++;
+            }
+            n = n * 128 + data[i];
+            sb.append(".");
+            sb.append(n);
+        }
+
+        return sb.toString();
+    }
+
+    int getPayloadLength(PDU pdu) throws Exception {
+        switch (pdu.getTag() & 0x1F) {
+        case EOC:
+            return 1;
+
+        case BOOLEAN:
+            return 1;
+
+        case INTEGER:
+            return size(pdu.getInt());
+
+        case UTF8_STRING:
+            String s = pdu.getString();
+            byte[] encoded = s.getBytes("UTF-8");
+            return encoded.length;
+
+        case IA5STRING:
+        case VISIBLE_STRING:
+        case UNIVERSAL_STRING:
+        case PRINTABLE_STRING:
+        case GENERALIZED_TIME:
+        case GRAPHIC_STRING:
+        case GENERAL_STRING:
+        case CHARACTER_STRING:
+        case UTCTIME:
+        case NUMERIC_STRING: {
+            String str = pdu.getString();
+            encoded = str.getBytes("ASCII");
+            return encoded.length;
+        }
+
+        case OBJECT_IDENTIFIER:
+        case REAL:
+        case BIT_STRING:
+            return pdu.getBytes().length;
+
+        case OCTET_STRING:
+        case NULL:
+        case OBJECT_DESCRIPTOR:
+        case EXTERNAL:
+        case ENUMERATED:
+        case EMBEDDED_PDV:
+        case RELATIVE_OID:
+        case T61_STRING:
+        case VIDEOTEX_STRING:
+        case BMP_STRING:
+            return pdu.getBytes().length;
+
+        default:
+            throw new IllegalArgumentException("Invalid type: " + pdu);
+        }
+    }
+
+    int size(long value) {
+        if (value < 128)
+            return 1;
+
+        if (value <= 0xFF)
+            return 2;
+
+        if (value <= 0xFFFF)
+            return 3;
+
+        if (value <= 0xFFFFFF)
+            return 4;
+
+        if (value <= 0xFFFFFFFF)
+            return 5;
+
+        if (value <= 0xFFFFFFFFFFL)
+            return 6;
+
+        if (value <= 0xFFFFFFFFFFFFL)
+            return 7;
+
+        if (value <= 0xFFFFFFFFFFFFFFL)
+            return 8;
+
+        if (value <= 0xFFFFFFFFFFFFFFFFL)
+            return 9;
+
+        throw new IllegalArgumentException("length too long");
+    }
+
+    public void write(OutputStream out, PDU pdu) throws Exception {
+        byte id = 0;
+
+        switch (pdu.getClss()) {
+        case UNIVERSAL:
+            id |= 0;
+            break;
+        case APPLICATION:
+            id |= 0x40;
+            break;
+        case CONTEXT:
+            id |= 0x80;
+            break;
+        case PRIVATE:
+            id |= 0xC0;
+            break;
+        }
+
+        if (pdu.isConstructed())
+            id |= 0x20;
+
+        int tag = pdu.getTag();
+        if (tag >= 0 && tag < 31) {
+            id |= tag;
+        } else {
+            throw new UnsupportedOperationException("Cant do tags > 30");
+        }
+
+        out.write(id);
+
+        int length = getPayloadLength(pdu);
+        int size = size(length);
+        if (size == 1) {
+            out.write(length);
+        } else {
+            out.write(size);
+            while (--size >= 0) {
+                byte data = (byte) ((length >> (size * 8)) & 0xFF);
+                out.write(data);
+            }
+        }
+        writePayload(out, pdu);
+    }
+
+    void writePayload(OutputStream out, PDU pdu) throws Exception {
+        switch (pdu.getTag()) {
+        case EOC:
+            out.write(0);
+            break;
+
+        case BOOLEAN:
+            if (pdu.getBoolean())
+                out.write(-1);
+            else
+                out.write(0);
+            break;
+
+        case ENUMERATED:
+        case INTEGER: {
+            long value = pdu.getInt();
+            int size = size(value);
+            for (int i = size; i >= 0; i--) {
+                byte b = (byte) ((value >> (i * 8)) & 0xFF);
+                out.write(b);
+            }
+        }
+
+        case BIT_STRING: {
+            byte bytes[] = pdu.getBytes();
+            int unused = bytes[0];
+            assert unused <= 7;
+            int[] mask = { 0xFF, 0x7F, 0x3F, 0x1F, 0xF, 0x7, 0x3, 0x1 };
+            bytes[bytes.length - 1] &= (byte) mask[unused];
+            out.write(bytes);
+            break;
+        }
+
+        case RELATIVE_OID:
+        case OBJECT_IDENTIFIER: {
+            int[] oid = pdu.getOID();
+            assert oid.length > 2;
+            assert oid[0] < 4;
+            assert oid[1] < 40;
+            byte top = (byte) (oid[0] * 40 + oid[1]);
+            out.write(top);
+            for (int i = 2; i < oid.length; i++) {
+                putOid(out,oid[i]);
+            }
+            break;
+        }
+
+        case OCTET_STRING: {
+            byte bytes[] = pdu.getBytes();
+            out.write(bytes);
+            break;
+        }
+
+        case NULL:
+            break;
+
+        case BMP_STRING:
+        case GRAPHIC_STRING:
+        case VISIBLE_STRING:
+        case GENERAL_STRING:
+        case UNIVERSAL_STRING:
+        case CHARACTER_STRING:
+        case NUMERIC_STRING:
+        case PRINTABLE_STRING:
+        case VIDEOTEX_STRING:
+        case T61_STRING:
+        case REAL:
+        case EMBEDDED_PDV:
+        case EXTERNAL:
+            throw new UnsupportedEncodingException("dont know real, embedded PDV or external");
+            
+        case UTF8_STRING: {
+            String s = pdu.getString();
+            byte [] data = s.getBytes("UTF-8");
+            out.write(data);
+            break;
+        }
+        
+        case OBJECT_DESCRIPTOR:
+        case IA5STRING:
+            String s = pdu.getString();
+            byte [] data = s.getBytes("ASCII");
+            out.write(data);
+            break;
+            
+            
+        case SEQUENCE:
+        case SET: {
+            PDU pdus[] = pdu.getChildren();
+            for ( PDU p : pdus ) {
+                write(out, p);
+            }
+        }
+            
+        
+        case UTCTIME:
+        case GENERALIZED_TIME:
+            Date date = pdu.getDate();
+            String ss= df.format(date);
+            byte d[] = ss.getBytes("ASCII");
+            out.write(d);
+            break;
+            
+        }
+    }
+    
+
+    private void putOid(OutputStream out, int i) throws IOException {
+        if (i > 127) {
+            putOid(out, i >> 7);
+            out.write(0x80 + (i & 0x7F));
+        } else
+            out.write(i & 0x7F);
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/asn1/PDU.java b/bundleplugin/src/main/java/aQute/libg/asn1/PDU.java
new file mode 100644
index 0000000..033928c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/asn1/PDU.java
@@ -0,0 +1,114 @@
+package aQute.libg.asn1;
+
+import java.util.*;
+
+public class PDU implements Types, Iterable<PDU> {
+    final int identifier;
+    final Object  payload;
+    byte data[] = new byte[100];
+
+
+    public PDU(int id, Object payload) {
+        identifier = id;
+        this.payload = payload;
+    }
+
+    public PDU(Date payload) {
+        identifier = UTCTIME;
+        this.payload = payload;
+    }
+
+    public PDU(int n) {
+        this(UNIVERSAL+INTEGER, n);
+    }
+
+    public PDU(boolean value) {
+        this(UNIVERSAL+BOOLEAN, value);
+    }
+
+    public PDU(String s) throws Exception {
+        this(UNIVERSAL+IA5STRING, s);
+    }
+    
+    public PDU(byte[] data) {
+        this(UNIVERSAL+OCTET_STRING, data);
+    }
+    
+    public PDU(BitSet bits) {
+        this(UNIVERSAL+BIT_STRING, bits);
+    }
+
+    public PDU(int top, int l1, int... remainder) {
+        identifier = UNIVERSAL+OBJECT_IDENTIFIER;
+        int[] ids = new int[remainder.length + 2];
+        ids[0] = top;
+        ids[1] = l1;
+        System.arraycopy(remainder, 0, ids, 2, remainder.length);
+        payload = ids;
+    }
+
+    public PDU(int tag, PDU... set) {
+        this(tag,(Object)set);
+    }
+
+    public PDU(PDU... set) {
+        this(SEQUENCE+CONSTRUCTED,set);
+    }
+
+
+    public int getTag() {
+        return identifier & TAGMASK;
+    }
+
+    int getClss() {
+        return identifier & CLASSMASK;
+    }
+
+    public boolean isConstructed() {
+        return (identifier & CONSTRUCTED) != 0;
+    }
+
+    public String getString() {
+        return (String) payload;
+    }
+
+    public Iterator<PDU> iterator() {
+        return Arrays.asList((PDU[]) payload).iterator();
+    }
+
+    
+    public int[] getOID() {
+        assert getTag() == OBJECT_IDENTIFIER;
+        return (int[]) payload;
+    }
+
+    public Boolean getBoolean() {
+        assert getTag() == BOOLEAN;
+        return (Boolean) payload;
+    }
+
+    public BitSet getBits() {
+        assert getTag() == BIT_STRING;
+        return (BitSet) payload;
+    }
+
+    public int getInt() {
+        assert getTag() == INTEGER || getTag() == ENUMERATED;
+        return (Integer) payload;
+    }
+
+    public byte[] getBytes() {
+        return (byte[]) payload;
+    }
+
+    public PDU[] getChildren() {
+        assert isConstructed();
+        return (PDU[]) payload;
+    }
+
+    public Date getDate() {
+        assert getTag() == UTCTIME || getTag() == GENERALIZED_TIME;
+        return (Date) payload;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/asn1/Types.java b/bundleplugin/src/main/java/aQute/libg/asn1/Types.java
new file mode 100644
index 0000000..630ed1c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/asn1/Types.java
@@ -0,0 +1,65 @@
+package aQute.libg.asn1;
+
+public interface Types {
+    int      UNIVERSAL         = 0x00000000;
+    int      APPLICATION       = 0x40000000;
+    int      CONTEXT           = 0x80000000;
+    int      PRIVATE           = 0xC0000000;
+    int      CLASSMASK         = 0xC0000000;
+    int      CONSTRUCTED       = 0x20000000;
+    int      TAGMASK           = 0x1FFFFFFF;
+
+    String [] CLASSES = {"U", "A", "C", "P"};
+    
+    // Payload Primitve
+    int      EOC               = 0;                                // null
+    // x
+    int      BOOLEAN           = 1;                                // Boolean
+    // x
+    int      INTEGER           = 2;                                // Long
+    // x
+    int      BIT_STRING        = 3;                                // byte
+    // [] -
+    int      OCTET_STRING      = 4;                                // byte
+    // [] -
+    int      NULL              = 5;                                // null
+    // x
+    int      OBJECT_IDENTIFIER = 6;                                // int[]
+    // x
+    int      OBJECT_DESCRIPTOR = 7;                                // 
+    int      EXTERNAL          = 8;                                //
+    int      REAL              = 9;                                // double
+    // x
+    int      ENUMERATED        = 10;                               // 
+    int      EMBEDDED_PDV      = 11;                               //
+    int      UTF8_STRING       = 12;                               // String
+    int      RELATIVE_OID      = 13;                               // 
+    int      SEQUENCE          = 16;                               // 
+    int      SET               = 17;
+    int      NUMERIC_STRING    = 18;                               // String
+    int      PRINTABLE_STRING  = 19;                               // String
+    int      T61_STRING        = 20;                               // String
+    int      VIDEOTEX_STRING   = 21;                               // String
+    int      IA5STRING         = 22;                               // String
+    int      UTCTIME           = 23;                               // Date
+    int      GENERALIZED_TIME  = 24;                               // Date
+    int      GRAPHIC_STRING    = 25;                               // String
+    int      VISIBLE_STRING    = 26;                               // String
+    int      GENERAL_STRING    = 27;                               // String
+    int      UNIVERSAL_STRING  = 28;                               // String
+    int      CHARACTER_STRING  = 29;                               // String
+    int      BMP_STRING        = 30;                               // byte[]
+
+    String[] TAGS              = { "EOC               ",
+            "BOOLEAN           ", "INTEGER           ", "BIT_STRING        ",
+            "OCTET_STRING      ", "NULL              ", "OBJECT_IDENTIFIER ",
+            "OBJECT_DESCRIPTOR ", "EXTERNAL          ", "REAL              ",
+            "ENUMERATED        ", "EMBEDDED_PDV      ", "UTF8_STRING       ",
+            "RELATIVE_OID      ", "?(14)             ", "?(15)             ",
+            "SEQUENCE          ", "SET               ", "NUMERIC_STRING    ",
+            "PRINTABLE_STRING  ", "T61_STRING        ", "VIDEOTEX_STRING   ",
+            "IA5STRING         ", "UTCTIME           ", "GENERALIZED_TIME  ",
+            "GRAPHIC_STRING    ", "VISIBLE_STRING    ", "GENERAL_STRING    ",
+            "UNIVERSAL_STRING  ", "CHARACTER_STRING  ", "BMP_STRING        ", };
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/asn1/algorithms b/bundleplugin/src/main/java/aQute/libg/asn1/algorithms
new file mode 100644
index 0000000..4fb6ab4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/asn1/algorithms
@@ -0,0 +1,22 @@
+---------------------------------------------------------------------------
+SignatureAlgorithm      digestAlgo        (I)  signatureAlgo (RFC 2630)
+                                          (II) digestEncrAlgo(PKCS#7)
+---------------------------------------------------------------------------
+(A) sha1WithRSA            1.3.14.3.2.26  (Ia)     1.2.840.113549.1.1.5
+   (1.2.840.113549.1.1.5)                 (Ib)     1.2.840.113549.1.1.1
+                                          (II)     1.2.840.113549.1.1.1
+
+
+(B) md5WithRSA           1.2.840.1x9.2.5  (Ia)     1.2.840.113549.1.1.4
+   (1.2.840.113549.1.1.4)                 (Ib)     1.2.840.113549.1.1.1
+                                          (II)     1.2.840.113549.1.1.1
+
+
+(C) ripeMD160WithRsa        1.3.36.3.2.1  (Ia)     1.3.36.3.3.1.2
+   (1.3.36.3.3.1.2)                       (Ib)     1.2.840.113549.1.1.1
+                                          (II)     1.2.840.113549.1.1.1
+
+
+(D) sha1WithDsa            1.3.14.3.2.26  (Ia)     1.2.840.10040.4.3
+   (1.2.840.10040.4.3)                    (Ib)     1.2.840.10040.4.1 (?)
+                                          (II)     1.2.840.10040.4.1
diff --git a/bundleplugin/src/main/java/aQute/libg/asn1/packageinfo b/bundleplugin/src/main/java/aQute/libg/asn1/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/asn1/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/cafs/CAFS.java b/bundleplugin/src/main/java/aQute/libg/cafs/CAFS.java
new file mode 100644
index 0000000..0bb8d7b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cafs/CAFS.java
@@ -0,0 +1,414 @@
+package aQute.libg.cafs;
+
+import static aQute.lib.io.IO.*;
+
+import java.io.*;
+import java.nio.channels.*;
+import java.security.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.util.zip.*;
+
+import aQute.lib.index.*;
+import aQute.libg.cryptography.*;
+
+/**
+ * CAFS implements a SHA-1 based file store. The basic idea is that every file
+ * in the universe has a unique SHA-1. Hard to believe but people smarter than
+ * me have come to that conclusion. This class maintains a compressed store of
+ * SHA-1 identified files. So if you have the SHA-1, you can get the contents.
+ * 
+ * This makes it easy to store a SHA-1 instead of the whole file or maintain a
+ * naming scheme. An added advantage is that it is always easy to verify you get
+ * the right stuff. The SHA-1 Content Addressable File Store is the core
+ * underlying idea in Git.
+ */
+public class CAFS implements Closeable, Iterable<SHA1> {
+	final static byte[]	CAFS			= "CAFS".getBytes();
+	final static byte[]	CAFE			= "CAFE".getBytes();
+	final static String	INDEXFILE		= "index.idx";
+	final static String	STOREFILE		= "store.cafs";
+	final static String	ALGORITHM		= "SHA-1";
+	final static int	KEYLENGTH		= 20;
+	final static int	HEADERLENGTH	= 4 // CAFS
+												+ 4 // flags
+												+ 4 // compressed length
+												+ 4 // uncompressed length
+												+ KEYLENGTH	// key
+												+ 2 // header checksum
+												;
+
+	final File			home;
+	Index				index;
+	RandomAccessFile	store;
+	FileChannel			channel;
+
+	/**
+	 * Constructor for a Content Addressable File Store
+	 * 
+	 * @param home
+	 * @param create
+	 * @throws Exception
+	 */
+	public CAFS(File home, boolean create) throws Exception {
+		this.home = home;
+		if (!home.isDirectory()) {
+			if (create) {
+				home.mkdirs();
+			} else
+				throw new IllegalArgumentException("CAFS requires a directory with create=false");
+		}
+
+		index = new Index(new File(home, INDEXFILE), KEYLENGTH);
+		store = new RandomAccessFile(new File(home, STOREFILE), "rw");
+		channel = store.getChannel();
+		if (store.length() < 0x100) {
+			if (create) {
+				store.write(CAFS);
+				for (int i = 1; i < 64; i++)
+					store.writeInt(0);
+				channel.force(true);
+			} else
+				throw new IllegalArgumentException("Invalid store file, length is too short "
+						+ store);
+			System.err.println(store.length());
+		}
+		store.seek(0);
+		if (!verifySignature(store, CAFS))
+			throw new IllegalArgumentException("Not a valid signature: CAFS at start of file");
+
+	}
+
+	/**
+	 * Store an input stream in the CAFS while calculating and returning the
+	 * SHA-1 code.
+	 * 
+	 * @param in
+	 *            The input stream to store.
+	 * @return The SHA-1 code.
+	 * @throws Exception
+	 *             if anything goes wrong
+	 */
+	public SHA1 write(InputStream in) throws Exception {
+
+		Deflater deflater = new Deflater();
+		MessageDigest md = MessageDigest.getInstance(ALGORITHM);
+		DigestInputStream din = new DigestInputStream(in, md);
+		ByteArrayOutputStream bout = new ByteArrayOutputStream();
+		DeflaterOutputStream dout = new DeflaterOutputStream(bout, deflater);
+		copy(din, dout);
+
+		synchronized (store) {
+			// First check if it already exists
+			SHA1 sha1 = new SHA1(md.digest());
+			
+			long search = index.search(sha1.digest());
+			if (search > 0)
+				return sha1;
+
+			byte[] compressed = bout.toByteArray();
+
+			// we need to append this file to our store,
+			// which requires a lock. However, we are in a race
+			// so others can get the lock between us getting
+			// the length and someone else getting the lock.
+			// So we must verify after we get the lock that the
+			// length was unchanged.
+			FileLock lock = null;
+			try {
+				long insertPoint;
+				int recordLength = compressed.length + HEADERLENGTH;
+
+				while (true) {
+					insertPoint = store.length();
+					lock = channel.lock(insertPoint, recordLength, false);
+
+					if (store.length() == insertPoint)
+						break;
+
+					// We got the wrong lock, someone else
+					// got in between reading the length
+					// and locking
+					lock.release();
+				}
+				int totalLength = deflater.getTotalIn();
+				store.seek(insertPoint);
+				update(sha1.digest(), compressed, totalLength);
+				index.insert(sha1.digest(), insertPoint);
+				return sha1;
+			} finally {
+				if (lock != null)
+					lock.release();
+			}
+		}
+	}
+
+	/**
+	 * Read the contents of a sha 1 key.
+	 * 
+	 * @param sha1
+	 *            The key
+	 * @return An Input Stream on the content or null of key not found
+	 * @throws Exception
+	 */
+	public InputStream read(final SHA1 sha1) throws Exception {
+		synchronized (store) {
+			long offset = index.search(sha1.digest());
+			if (offset < 0)
+				return null;
+
+			byte[] readSha1;
+			byte[] buffer;
+			store.seek(offset);
+			if (!verifySignature(store, CAFE))
+				throw new IllegalArgumentException("No signature");
+
+			int flags =store.readInt();
+			int compressedLength = store.readInt();
+			int uncompressedLength = store.readInt();
+			readSha1 = new byte[KEYLENGTH];
+			store.read(readSha1);
+			SHA1 rsha1 = new SHA1(readSha1);
+			
+			if (!sha1.equals(rsha1))
+				throw new IOException("SHA1 read and asked mismatch: " + sha1 + " " + rsha1);
+
+			short crc = store.readShort(); // Read CRC
+			if ( crc != checksum(flags,compressedLength, uncompressedLength, readSha1))
+				throw new IllegalArgumentException("Invalid header checksum: " + sha1);
+			
+			buffer = new byte[compressedLength];
+			store.readFully(buffer);
+			return getSha1Stream(sha1, buffer, uncompressedLength);
+		}
+	}
+
+	public boolean exists(byte[] sha1) throws Exception {
+		return index.search(sha1) >= 0;
+	}
+
+	public void reindex() throws Exception {
+		long length;
+		synchronized (store) {
+			length = store.length();
+			if (length < 0x100)
+				throw new IllegalArgumentException(
+						"Store file is too small, need to be at least 256 bytes: " + store);
+		}
+
+		RandomAccessFile in = new RandomAccessFile(new File(home, STOREFILE), "r");
+		try {
+			byte[] signature = new byte[4];
+			in.readFully(signature);
+			if (!Arrays.equals(CAFS, signature))
+				throw new IllegalArgumentException("Store file does not start with CAFS: " + in);
+
+			in.seek(0x100);
+			File ixf = new File(home, "index.new");
+			Index index = new Index(ixf, KEYLENGTH);
+
+			while (in.getFilePointer() < length) {
+				long entry = in.getFilePointer();
+				SHA1 sha1 = verifyEntry(in);
+				index.insert(sha1.digest(), entry);
+			}
+
+			synchronized (store) {
+				index.close();
+				File indexFile = new File(home, INDEXFILE);
+				ixf.renameTo(indexFile);
+				this.index = new Index(indexFile, KEYLENGTH);
+			}
+		} finally {
+			in.close();
+		}
+	}
+
+	public void close() throws IOException {
+		synchronized (store) {
+			try {
+				store.close();
+			} finally {
+				index.close();
+			}
+		}
+	}
+
+	private SHA1 verifyEntry(RandomAccessFile in) throws IOException, NoSuchAlgorithmException {
+		byte[] signature = new byte[4];
+		in.readFully(signature);
+		if (!Arrays.equals(CAFE, signature))
+			throw new IllegalArgumentException("File is corrupted: " + in);
+
+		/* int flags = */in.readInt();
+		int compressedSize = in.readInt();
+		int uncompressedSize = in.readInt();
+		byte[] key = new byte[KEYLENGTH];
+		in.readFully(key);
+		SHA1 sha1 = new SHA1(key);
+		
+		byte[] buffer = new byte[compressedSize];
+		in.readFully(buffer);
+
+		InputStream xin = getSha1Stream(sha1, buffer, uncompressedSize);
+		xin.skip(uncompressedSize);
+		xin.close();
+		return sha1;
+	}
+
+	private boolean verifySignature(DataInput din, byte[] org) throws IOException {
+		byte[] read = new byte[org.length];
+		din.readFully(read);
+		return Arrays.equals(read, org);
+	}
+
+	private InputStream getSha1Stream(final SHA1 sha1, byte[] buffer, final int total)
+			throws NoSuchAlgorithmException {
+		ByteArrayInputStream in = new ByteArrayInputStream(buffer);
+		InflaterInputStream iin = new InflaterInputStream(in) {
+			int count = 0;
+			final MessageDigest digestx = MessageDigest.getInstance(ALGORITHM);
+			final AtomicBoolean calculated = new AtomicBoolean();
+			
+			@Override public int read(byte[] data, int offset, int length) throws IOException {
+				int size = super.read(data, offset, length);
+				if (size <= 0)
+					eof();
+				else {
+					count+=size;
+					this.digestx.update(data, offset, size);
+				}
+				return size;
+			}
+
+			public int read() throws IOException {
+				int c = super.read();
+				if (c < 0)
+					eof();
+				else {
+					count++;
+					this.digestx.update((byte) c);
+				}
+				return c;
+			}
+
+			void eof() throws IOException {
+				if ( calculated.getAndSet(true))
+					return;
+				
+				if ( count != total )
+					throw new IOException("Counts do not match. Expected to read: " + total + " Actually read: " + count);
+				
+				SHA1 calculatedSha1 = new SHA1(digestx.digest());
+				if (!sha1.equals(calculatedSha1))
+					throw ( new IOException("SHA1 caclulated and asked mismatch, asked: "
+							+ sha1 + ", \nfound: " +calculatedSha1));
+			}
+
+			public void close() throws IOException {
+				eof();
+				super.close();
+			}
+		};
+		return iin;
+	}
+
+	/**
+	 * Update a record in the store, assuming the store is at the right
+	 * position.
+	 * 
+	 * @param sha1
+	 *            The checksum
+	 * @param compressed
+	 *            The compressed length
+	 * @param totalLength
+	 *            The uncompressed length
+	 * @throws IOException
+	 *             The exception
+	 */
+	private void update(byte[] sha1, byte[] compressed, int totalLength) throws IOException {
+		//System.err.println("pos: " + store.getFilePointer());
+		store.write(CAFE); // 00-03 Signature
+		store.writeInt(0); // 04-07 Flags for the future
+		store.writeInt(compressed.length); // 08-11 Length deflated data
+		store.writeInt(totalLength); // 12-15 Length
+		store.write(sha1); // 16-35
+		store.writeShort( checksum(0,compressed.length, totalLength, sha1));
+		store.write(compressed);
+		channel.force(false);
+	}
+
+	
+	
+	private short checksum(int flags, int compressedLength, int totalLength, byte[] sha1) {
+		CRC32 crc = new CRC32();
+		crc.update(flags);
+		crc.update(flags>>8);
+		crc.update(flags>>16);
+		crc.update(flags>>24);
+		crc.update(compressedLength);
+		crc.update(compressedLength>>8);
+		crc.update(compressedLength>>16);
+		crc.update(compressedLength>>24);
+		crc.update(totalLength);
+		crc.update(totalLength>>8);
+		crc.update(totalLength>>16);
+		crc.update(totalLength>>24);
+		crc.update(sha1);
+		return (short) crc.getValue();
+	}
+
+	public Iterator<SHA1> iterator() {
+		
+		return new Iterator<SHA1>() {
+			long position = 0x100;
+			
+			public boolean hasNext() {
+				synchronized(store) {
+					try {
+						return position < store.length();
+					} catch (IOException e) {
+						throw new RuntimeException(e);
+					}
+				}
+			}
+
+			public SHA1 next() {
+				synchronized(store) {
+					try {
+						store.seek(position);
+						byte [] signature = new byte[4];
+						store.readFully(signature);
+						if ( !Arrays.equals(CAFE, signature))
+							throw new IllegalArgumentException("No signature");
+
+						int flags = store.readInt();
+						int compressedLength = store.readInt();
+						int totalLength = store.readInt();
+						byte []sha1 = new byte[KEYLENGTH];
+						store.readFully(sha1);
+						short crc = store.readShort();
+						if ( crc != checksum(flags,compressedLength, totalLength, sha1))
+							throw new IllegalArgumentException("Header checksum fails");
+						
+						position += HEADERLENGTH + compressedLength;
+						return new SHA1(sha1);
+					} catch (IOException e) {
+						throw new RuntimeException(e);
+					}				
+				}
+			}
+
+			public void remove() {
+				throw new UnsupportedOperationException("Remvoe not supported, CAFS is write once");
+			}			
+		};
+	}
+
+	
+	public boolean isEmpty() throws IOException {
+		synchronized(store) {
+			return store.getFilePointer() <= 256;
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cafs/packageinfo b/bundleplugin/src/main/java/aQute/libg/cafs/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cafs/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/classdump/ClassDumper.java b/bundleplugin/src/main/java/aQute/libg/classdump/ClassDumper.java
new file mode 100755
index 0000000..e45cd39
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/classdump/ClassDumper.java
@@ -0,0 +1,698 @@
+/* Copyright 2006 aQute SARL 
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.libg.classdump;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+public class ClassDumper {
+    /**
+     * <pre>
+     * ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its
+     * package. 
+     * ACC_FINAL 0x0010 Declared final; no subclasses allowed.
+     * ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the
+     * invokespecial instruction. 
+     * ACC_INTERFACE 0x0200 Is an interface, not a
+     * class. 
+     * ACC_ABSTRACT 0x0400 Declared abstract; may not be instantiated.
+     * </pre>
+     * 
+     * @param mod
+     */
+    final static int ACC_PUBLIC  = 0x0001; // Declared public; may be accessed
+                                            // from outside its package.
+    final static int ACC_FINAL     = 0x0010; // Declared final; no subclasses
+    // allowed.
+    final static int ACC_SUPER     = 0x0020; // Treat superclass methods
+    // specially when invoked by the
+    // invokespecial instruction.
+    final static int ACC_INTERFACE = 0x0200; // Is an interface, not a classs
+    final static int ACC_ABSTRACT  = 0x0400; // Declared abstract; may not be
+                                                // instantiated.
+    
+    final static class Assoc {
+        Assoc(byte tag, int a, int b) {
+            this.tag = tag;
+            this.a = a;
+            this.b = b;
+        }
+
+        byte tag;
+        int  a;
+        int  b;
+
+    }
+
+    final String        path;
+    final static String NUM_COLUMN = "%-30s %d\n";
+    final static String HEX_COLUMN = "%-30s %x\n";
+    final static String STR_COLUMN = "%-30s %s\n";
+
+    PrintStream         ps         = System.err;
+    Object[]            pool;
+    InputStream         in;
+
+    public ClassDumper(String path) throws Exception {
+        this(path, new FileInputStream(new File(path)));
+    }
+
+    public ClassDumper(String path, InputStream in) throws IOException {
+        this.path = path;
+        this.in = in;
+    }
+
+    public void dump(PrintStream ps) throws Exception {
+        if (ps != null)
+            this.ps = ps;
+        DataInputStream din = new DataInputStream(in);
+        parseClassFile(din);
+        din.close();
+    }
+
+    void parseClassFile(DataInputStream in) throws IOException {
+        int magic = in.readInt();
+        if (magic != 0xCAFEBABE)
+            throw new IOException("Not a valid class file (no CAFEBABE header)");
+
+        ps.printf(HEX_COLUMN, "magic", magic);
+        int minor = in.readUnsignedShort(); // minor version
+        int major = in.readUnsignedShort(); // major version
+        ps.printf(STR_COLUMN, "version", "" + major + "." + minor);
+        int pool_size = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, "pool size", pool_size);
+        pool = new Object[pool_size];
+
+        process: for (int poolIndex = 1; poolIndex < pool_size; poolIndex++) {
+            byte tag = in.readByte();
+
+            switch (tag) {
+            case 0:
+                ps.printf("%30d tag (0)\n", poolIndex);
+                break process;
+
+            case 1:
+                String name = in.readUTF();
+                pool[poolIndex] = name;
+                ps.printf("%30d tag(1) utf8 '%s'\n", poolIndex, name);
+                break;
+
+            case 2:
+                throw new IOException("Invalid tag " + tag);
+
+            case 3:
+                int i = in.readInt();
+                pool[poolIndex] = Integer.valueOf(i);
+                ps.printf("%30d tag(3) int %s\n", poolIndex, i);
+                break;
+
+            case 4:
+                float f = in.readFloat();
+                pool[poolIndex] = new Float(f);
+                ps.printf("%30d tag(4) float %s\n", poolIndex, f);
+                break;
+
+            // For some insane optimization reason are
+            // the long and the double two entries in the
+            // constant pool. See 4.4.5
+            case 5:
+                long l = in.readLong();
+                pool[poolIndex] = Long.valueOf(l);
+                ps.printf("%30d tag(5) long %s\n", poolIndex, l);
+                poolIndex++;
+                break;
+
+            case 6:
+                double d = in.readDouble();
+                pool[poolIndex] = new Double(d);
+                ps.printf("%30d tag(6) double %s\n", poolIndex, d);
+                poolIndex++;
+                break;
+
+            case 7:
+                int class_index = in.readUnsignedShort();
+                pool[poolIndex] = Integer.valueOf(class_index);
+                ps.printf("%30d tag(7) constant classs %d\n", poolIndex,
+                        class_index);
+                break;
+
+            case 8:
+                int string_index = in.readUnsignedShort();
+                pool[poolIndex] = Integer.valueOf(string_index);
+                ps.printf("%30d tag(8) constant string %d\n", poolIndex,
+                        string_index);
+                break;
+
+            case 9: // Field ref
+                class_index = in.readUnsignedShort();
+                int name_and_type_index = in.readUnsignedShort();
+                pool[poolIndex] = new Assoc((byte) 9, class_index,
+                        name_and_type_index);
+                ps.printf("%30d tag(9) field ref %d/%d\n", poolIndex,
+                        class_index, name_and_type_index);
+                break;
+
+            case 10: // Method ref
+                class_index = in.readUnsignedShort();
+                name_and_type_index = in.readUnsignedShort();
+                pool[poolIndex] = new Assoc((byte) 10, class_index,
+                        name_and_type_index);
+                ps.printf("%30d tag(10) method ref %d/%d\n", poolIndex,
+                        class_index, name_and_type_index);
+                break;
+
+            case 11: // Interface and Method ref
+                class_index = in.readUnsignedShort();
+                name_and_type_index = in.readUnsignedShort();
+                pool[poolIndex] = new Assoc((byte) 11, class_index,
+                        name_and_type_index);
+                ps.printf("%30d tag(11) interface and method ref %d/%d\n",
+                        poolIndex, class_index, name_and_type_index);
+                break;
+
+            // Name and Type
+            case 12:
+                int name_index = in.readUnsignedShort();
+                int descriptor_index = in.readUnsignedShort();
+                pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
+                ps.printf("%30d tag(12) name and type %d/%d\n", poolIndex,
+                        name_index, descriptor_index);
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown tag: " + tag);
+            }
+        }
+
+        int access = in.readUnsignedShort(); // access
+        printAccess(access);
+        int this_class = in.readUnsignedShort();
+        int super_class = in.readUnsignedShort();
+        ps.printf("%-30s %x %s(#%d)\n", "this_class", access, pool[this_class],
+                this_class);
+        ps.printf("%-30s %s(#%d)\n", "super_class", pool[super_class],
+                super_class);
+
+        int interfaces_count = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, "interface count", interfaces_count);
+        for (int i = 0; i < interfaces_count; i++) {
+            int interface_index = in.readUnsignedShort();
+            ps.printf("%-30s interface %s(#%d)", "interface count",
+                    pool[interface_index], interfaces_count);
+        }
+
+        int field_count = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, "field count", field_count);
+        for (int i = 0; i < field_count; i++) {
+            access = in.readUnsignedShort(); // access
+            printAccess(access);
+            int name_index = in.readUnsignedShort();
+            int descriptor_index = in.readUnsignedShort();
+            ps.printf("%-30s %x %s(#%d) %s(#%d)\n", "field def", access,
+                    pool[name_index], name_index, pool[descriptor_index],
+                    descriptor_index);
+            doAttributes(in, "  ");
+        }
+
+        int method_count = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, "method count", method_count);
+        for (int i = 0; i < method_count; i++) {
+            int access_flags = in.readUnsignedShort();
+            printAccess(access_flags);
+            int name_index = in.readUnsignedShort();
+            int descriptor_index = in.readUnsignedShort();
+            ps.printf("%-30s %x %s(#%d) %s(#%d)\n", "method def", access_flags,
+                    pool[name_index], name_index, pool[descriptor_index],
+                    descriptor_index);
+            doAttributes(in, "  ");
+        }
+
+        doAttributes(in, "");
+        if (in.read() >= 0)
+            ps.printf("Extra bytes follow ...");
+    }
+
+    /**
+     * Called for each attribute in the class, field, or method.
+     * 
+     * @param in
+     *            The stream
+     * @throws IOException
+     */
+    private void doAttributes(DataInputStream in, String indent)
+            throws IOException {
+        int attribute_count = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, indent + "attribute count", attribute_count);
+        for (int j = 0; j < attribute_count; j++) {
+            doAttribute(in, indent + j + ": ");
+        }
+    }
+
+    /**
+     * Process a single attribute, if not recognized, skip it.
+     * 
+     * @param in
+     *            the data stream
+     * @throws IOException
+     */
+    private void doAttribute(DataInputStream in, String indent)
+            throws IOException {
+        int attribute_name_index = in.readUnsignedShort();
+        long attribute_length = in.readInt();
+        attribute_length &= 0xFFFF;
+        String attributeName = (String) pool[attribute_name_index];
+        ps.printf("%-30s %s(#%d)\n", indent + "attribute", attributeName,
+                attribute_name_index);
+        if ("RuntimeVisibleAnnotations".equals(attributeName))
+            doAnnotations(in, indent);
+        else if ("SourceFile".equals(attributeName))
+            doSourceFile(in, indent);
+        else if ("Code".equals(attributeName))
+            doCode(in, indent);
+        else if ("LineNumberTable".equals(attributeName))
+            doLineNumberTable(in, indent);
+        else if ("LocalVariableTable".equals(attributeName))
+            doLocalVariableTable(in, indent);
+        else if ("InnerClasses".equals(attributeName))
+            doInnerClasses(in, indent);
+        else if ("Exceptions".equals(attributeName))
+            doExceptions(in, indent);
+        else if ("EnclosingMethod".equals(attributeName))
+            doEnclosingMethod(in, indent);
+        else if ("Signature".equals(attributeName))
+            doSignature(in, indent);
+        else if ("Synthetic".equals(attributeName))
+            ; // Is empty!
+        else if ("Deprecated".equals(attributeName))
+            ; // Is Empty
+        else {
+            ps.printf("%-30s %d\n", indent + "Unknown attribute, skipping",
+                    attribute_length);
+            if (attribute_length > 0x7FFFFFFF) {
+                throw new IllegalArgumentException("Attribute > 2Gb");
+            }
+            byte buffer[] = new byte[(int) attribute_length];
+            in.readFully(buffer);
+            printHex(buffer);
+        }
+    }
+
+    /**
+     * <pre>
+     * Signature_attribute {
+     * 	u2 attribute_name_index;
+     * 	u4 attribute_length;
+     * 	u2 signature_index;
+     * 	}
+     * </pre>
+     * 
+     * @param in
+     * @param indent
+     */
+    void doSignature(DataInputStream in, String indent) throws IOException {
+        int signature_index = in.readUnsignedShort();
+        ps.printf("%-30s %s(#%d)\n", indent + "signature",
+                pool[signature_index], signature_index);
+    }
+
+    /**
+     * <pre>
+     * EnclosingMethod_attribute {
+     * 	u2 attribute_name_index;
+     * 	u4 attribute_length;
+     * 	u2 class_index
+     * 	u2 method_index;
+     * 	}
+     * 	
+     * </pre>
+     */
+    void doEnclosingMethod(DataInputStream in, String indent)
+            throws IOException {
+        int class_index = in.readUnsignedShort();
+        int method_index = in.readUnsignedShort();
+        ps.printf("%-30s %s(#%d/c) %s\n", //
+        		indent + "enclosing method", //
+                pool[((Integer) pool[class_index]).intValue()], //
+                class_index, //
+                (method_index == 0 ? "<>" : pool[method_index]));
+    }
+
+    /**
+     * <pre>
+     *  Exceptions_attribute {
+     * 		u2 attribute_name_index;
+     * 		u4 attribute_length;
+     * 		u2 number_of_exceptions;
+     * 		u2 exception_index_table[number_of_exceptions];
+     * 	}
+     * </pre>
+     * 
+     * @param in
+     * @param indent
+     */
+    private void doExceptions(DataInputStream in, String indent)
+            throws IOException {
+        int number_of_exceptions = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, indent + "number of exceptions",
+                number_of_exceptions);
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+        for (int i = 0; i < number_of_exceptions; i++) {
+            int exception_index_table = in.readUnsignedShort();
+            sb.append(del);
+            sb.append(pool[((Integer) pool[exception_index_table])]);
+            sb.append("(#");
+            sb.append(exception_index_table);
+            sb.append("/c)");
+            del = ", ";
+        }
+        ps.printf("%-30s %d: %s\n", indent + "exceptions",
+                number_of_exceptions, sb);
+    }
+
+    /**
+     * <pre>
+     * Code_attribute {
+     * 		u2 attribute_name_index;
+     * 		u4 attribute_length;
+     * 		u2 max_stack;
+     * 		u2 max_locals;
+     * 		u4 code_length;
+     * 		u1 code[code_length];
+     * 		u2 exception_table_length;
+     * 		{    	u2 start_pc;
+     * 		      	u2 end_pc;
+     * 		      	u2  handler_pc;
+     * 		      	u2  catch_type;
+     * 		}	exception_table[exception_table_length];
+     * 		u2 attributes_count;
+     * 		attribute_info attributes[attributes_count];
+     * 	}
+     * </pre>
+     * 
+     * @param in
+     * @param pool
+     * @throws IOException
+     */
+    private void doCode(DataInputStream in, String indent) throws IOException {
+        int max_stack = in.readUnsignedShort();
+        int max_locals = in.readUnsignedShort();
+        int code_length = in.readInt();
+        ps.printf(NUM_COLUMN, indent + "max_stack", max_stack);
+        ps.printf(NUM_COLUMN, indent + "max_locals", max_locals);
+        ps.printf(NUM_COLUMN, indent + "code_length", code_length);
+        byte code[] = new byte[code_length];
+        in.readFully(code);
+        printHex(code);
+        int exception_table_length = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, indent + "exception_table_length",
+                exception_table_length);
+
+        for (int i = 0; i < exception_table_length; i++) {
+            int start_pc = in.readUnsignedShort();
+            int end_pc = in.readUnsignedShort();
+            int handler_pc = in.readUnsignedShort();
+            int catch_type = in.readUnsignedShort();
+            ps.printf("%-30s %d/%d/%d/%d\n", indent + "exception_table",
+                    start_pc, end_pc, handler_pc, catch_type);
+        }
+        doAttributes(in, indent + "  ");
+    }
+
+    /**
+     * We must find Class.forName references ...
+     * 
+     * @param code
+     */
+    protected void printHex(byte[] code) {
+        int index = 0;
+        while (index < code.length) {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < 16 && index < code.length; i++) {
+                String s = Integer.toHexString((0xFF & code[index++]))
+                        .toUpperCase();
+                if (s.length() == 1)
+                    sb.append("0");
+                sb.append(s);
+                sb.append(" ");
+            }
+            ps.printf(STR_COLUMN, "", sb.toString());
+        }
+    }
+
+    private void doSourceFile(DataInputStream in, String indent)
+            throws IOException {
+        int sourcefile_index = in.readUnsignedShort();
+        ps.printf("%-30s %s(#%d)\n", indent + "Source file",
+                pool[sourcefile_index], sourcefile_index);
+    }
+
+    private void doAnnotations(DataInputStream in, String indent)
+            throws IOException {
+        int num_annotations = in.readUnsignedShort(); // # of annotations
+        ps
+                .printf(NUM_COLUMN, indent + "Number of annotations",
+                        num_annotations);
+        for (int a = 0; a < num_annotations; a++) {
+            doAnnotation(in, indent);
+        }
+    }
+
+    private void doAnnotation(DataInputStream in, String indent)
+            throws IOException {
+        int type_index = in.readUnsignedShort();
+        ps.printf("%-30s %s(#%d)", indent + "type", pool[type_index],
+                type_index);
+        int num_element_value_pairs = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, indent + "num_element_value_pairs",
+                num_element_value_pairs);
+        for (int v = 0; v < num_element_value_pairs; v++) {
+            int element_name_index = in.readUnsignedShort();
+            ps.printf(NUM_COLUMN, indent + "element_name_index",
+                    element_name_index);
+            doElementValue(in, indent);
+        }
+    }
+
+    private void doElementValue(DataInputStream in, String indent)
+            throws IOException {
+        int tag = in.readUnsignedByte();
+        switch (tag) {
+        case 'B':
+        case 'C':
+        case 'D':
+        case 'F':
+        case 'I':
+        case 'J':
+        case 'S':
+        case 'Z':
+        case 's':
+            int const_value_index = in.readUnsignedShort();
+            ps.printf("%-30s %c %s(#%d)\n", indent + "element value", tag,
+                    pool[const_value_index], const_value_index);
+            break;
+
+        case 'e':
+            int type_name_index = in.readUnsignedShort();
+            int const_name_index = in.readUnsignedShort();
+            ps.printf("%-30s %c %s(#%d) %s(#%d)\n", indent + "type+const", tag,
+                    pool[type_name_index], type_name_index,
+                    pool[const_name_index], const_name_index);
+            break;
+
+        case 'c':
+            int class_info_index = in.readUnsignedShort();
+            ps.printf("%-30s %c %s(#%d)\n", indent + "element value", tag,
+                    pool[class_info_index], class_info_index);
+            break;
+
+        case '@':
+            ps.printf("%-30s %c\n", indent + "sub annotation", tag);
+            doAnnotation(in, indent);
+            break;
+
+        case '[':
+            int num_values = in.readUnsignedShort();
+            ps.printf("%-30s %c num_values=%d\n", indent + "sub element value",
+                    tag, num_values);
+            for (int i = 0; i < num_values; i++) {
+                doElementValue(in, indent);
+            }
+            break;
+
+        default:
+            throw new IllegalArgumentException(
+                    "Invalid value for Annotation ElementValue tag " + tag);
+        }
+    }
+
+    /**
+     * <pre>
+     *  LineNumberTable_attribute {
+     * 		u2 attribute_name_index;
+     * 		u4 attribute_length;
+     * 		u2 line_number_table_length;
+     * 		{  u2 start_pc;	     
+     * 		   u2 line_number;	     
+     * 		} line_number_table[line_number_table_length];
+     * 	}
+     * 	
+     * </pre>
+     */
+    void doLineNumberTable(DataInputStream in, String indent)
+            throws IOException {
+        int line_number_table_length = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, indent + "line number table length",
+                line_number_table_length);
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < line_number_table_length; i++) {
+            int start_pc = in.readUnsignedShort();
+            int line_number = in.readUnsignedShort();
+            sb.append(start_pc);
+            sb.append("/");
+            sb.append(line_number);
+            sb.append(" ");
+        }
+        ps.printf("%-30s %d: %s\n", indent + "line number table",
+                line_number_table_length, sb);
+    }
+
+    /**
+     * 
+     * <pre>
+     * 	LocalVariableTable_attribute {
+     * 		u2 attribute_name_index;
+     * 		u4 attribute_length;
+     * 		u2 local_variable_table_length;
+     * 		{  u2 start_pc;
+     * 		    u2 length;
+     * 		    u2 name_index;
+     * 		    u2 descriptor_index;
+     * 		    u2 index;
+     * 		} local_variable_table[local_variable_table_length];
+     * 	}	
+     * </pre>
+     */
+
+    void doLocalVariableTable(DataInputStream in, String indent)
+            throws IOException {
+        int local_variable_table_length = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, indent + "local variable table length",
+                local_variable_table_length);
+        for (int i = 0; i < local_variable_table_length; i++) {
+            int start_pc = in.readUnsignedShort();
+            int length = in.readUnsignedShort();
+            int name_index = in.readUnsignedShort();
+            int descriptor_index = in.readUnsignedShort();
+            int index = in.readUnsignedShort();
+            ps.printf("%-30s %d: %d/%d %s(#%d) %s(#%d)\n", indent, index,
+                    start_pc, length, pool[name_index], name_index,
+                    pool[descriptor_index], descriptor_index);
+        }
+    }
+
+    /**
+     * <pre>
+     *    InnerClasses_attribute {
+     * 		u2 attribute_name_index;
+     * 		u4 attribute_length;
+     * 		u2 number_of_classes;
+     * 		{  u2 inner_class_info_index;	     
+     * 		   u2 outer_class_info_index;	     
+     * 		   u2 inner_name_index;	     
+     * 		   u2 inner_class_access_flags;	     
+     * 		} classes[number_of_classes];
+     * 	}
+     * </pre>
+     * 
+     */
+    void doInnerClasses(DataInputStream in, String indent) throws IOException {
+        int number_of_classes = in.readUnsignedShort();
+        ps.printf(NUM_COLUMN, indent + "number of classes", number_of_classes);
+        for (int i = 0; i < number_of_classes; i++) {
+            int inner_class_info_index = in.readUnsignedShort();
+            int outer_class_info_index = in.readUnsignedShort();
+            int inner_name_index = in.readUnsignedShort();
+            int inner_class_access_flags = in.readUnsignedShort();
+            printAccess(inner_class_access_flags);
+
+            String iname = "<>";
+            String oname = iname;
+
+            if (inner_class_info_index != 0)
+                iname = (String) pool[((Integer) pool[inner_class_info_index])
+                        .intValue()];
+            if (outer_class_info_index != 0)
+                oname = (String) pool[((Integer) pool[outer_class_info_index])
+                        .intValue()];
+
+            ps.printf("%-30s %d: %x %s(#%d/c) %s(#%d/c) %s(#%d) \n", indent, i,
+                    inner_class_access_flags, iname, inner_class_info_index,
+                    oname, outer_class_info_index, pool[inner_name_index],
+                    inner_name_index);
+        }
+    }
+
+
+    void printClassAccess(int mod) {
+        ps.printf("%-30s", "Class Access");
+        if ((ACC_PUBLIC&mod)!= 0)
+            ps.print(" public");
+        if ((ACC_FINAL&mod)!= 0)
+            ps.print(" final");
+        if ((ACC_SUPER&mod)!= 0)
+            ps.print(" super");
+        if ((ACC_INTERFACE&mod)!= 0)
+            ps.print(" interface");
+        if ((ACC_ABSTRACT&mod)!= 0)
+            ps.print(" abstract");
+
+        ps.println();
+    }
+
+    void printAccess(int mod) {
+        ps.printf("%-30s", "Access");
+        if (Modifier.isStatic(mod))
+            ps.print(" static");
+        if (Modifier.isAbstract(mod))
+            ps.print(" abstract");
+        if (Modifier.isPublic(mod))
+            ps.print(" public");
+        if (Modifier.isFinal(mod))
+            ps.print(" final");
+        if (Modifier.isInterface(mod))
+            ps.print(" interface");
+        if (Modifier.isNative(mod))
+            ps.print(" native");
+        if (Modifier.isPrivate(mod))
+            ps.print(" private");
+        if (Modifier.isProtected(mod))
+            ps.print(" protected");
+        if (Modifier.isStrict(mod))
+            ps.print(" strict");
+        if (Modifier.isSynchronized(mod))
+            ps.print(" synchronized");
+        if (Modifier.isTransient(mod))
+            ps.print(" transient");
+        if (Modifier.isVolatile(mod))
+            ps.print(" volatile");
+
+        ps.println();
+    }
+
+    public static void main(String args[]) throws Exception {
+        if (args.length == 0) {
+            System.err.println("clsd <class file>+");
+        }
+        for (int i = 0; i < args.length; i++) {
+            File f = new File(args[i]);
+            if (!f.isFile())
+                System.err.println("File does not exist or is directory " + f);
+            else {
+                ClassDumper cd = new ClassDumper(args[i]);
+                cd.dump(null);
+            }
+        }
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/classdump/packageinfo b/bundleplugin/src/main/java/aQute/libg/classdump/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/classdump/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/classloaders/URLClassLoaderWrapper.java b/bundleplugin/src/main/java/aQute/libg/classloaders/URLClassLoaderWrapper.java
new file mode 100644
index 0000000..10ddca5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/classloaders/URLClassLoaderWrapper.java
@@ -0,0 +1,27 @@
+package aQute.libg.classloaders;
+
+import java.lang.reflect.*;
+import java.net.*;
+
+public class URLClassLoaderWrapper {
+	final URLClassLoader loader;
+	final Method addURL;
+	
+	public URLClassLoaderWrapper(ClassLoader loader) throws Exception {
+		this.loader = (URLClassLoader) loader;
+		addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+		addURL.setAccessible(true);
+	}
+	
+	public void addURL(URL url) throws Exception  {
+		try {
+		addURL.invoke(loader, url);
+		} catch( InvocationTargetException ite) {
+			throw (Exception) ite.getTargetException();
+		}
+	}
+	
+	public Class<?> loadClass(String name) throws Exception {
+		return loader.loadClass(name);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/classloaders/packageinfo b/bundleplugin/src/main/java/aQute/libg/classloaders/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/classloaders/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
diff --git a/bundleplugin/src/main/java/aQute/libg/clauses/Clause.java b/bundleplugin/src/main/java/aQute/libg/clauses/Clause.java
new file mode 100755
index 0000000..bab9fee
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/clauses/Clause.java
@@ -0,0 +1,8 @@
+package aQute.libg.clauses;
+
+import java.util.*;
+
+public class Clause extends LinkedHashMap<String,String> {
+	private static final long	serialVersionUID	= 1L;
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/clauses/Clauses.java b/bundleplugin/src/main/java/aQute/libg/clauses/Clauses.java
new file mode 100755
index 0000000..12eba4f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/clauses/Clauses.java
@@ -0,0 +1,65 @@
+package aQute.libg.clauses;
+
+import java.util.*;
+
+import aQute.libg.log.*;
+import aQute.libg.qtokens.*;
+
+public class Clauses extends LinkedHashMap<String,Map<String,String>>{
+	private static final long	serialVersionUID	= 1L;
+	
+	/**
+	 * Standard OSGi header parser. This parser can handle the format clauses
+	 * ::= clause ( ',' clause ) + clause ::= name ( ';' name ) (';' key '='
+	 * value )
+	 * 
+	 * This is mapped to a Map { name => Map { attr|directive => value } }
+	 * 
+	 * @param value
+	 * @return
+	 * @throws MojoExecutionException
+	 */
+	static public Clauses parse(String value, Logger logger) {
+		if (value == null || value.trim().length() == 0)
+			return new Clauses();
+
+		Clauses result = new Clauses();
+		QuotedTokenizer qt = new QuotedTokenizer(value, ";=,");
+		char del;
+		do {
+			boolean hadAttribute = false;
+			Clause clause = new Clause();
+			List<String> aliases = new ArrayList<String>();
+			aliases.add(qt.nextToken());
+			del = qt.getSeparator();
+			while (del == ';') {
+				String adname = qt.nextToken();
+				if ((del = qt.getSeparator()) != '=') {
+					if (hadAttribute)
+						throw new IllegalArgumentException(
+								"Header contains name field after attribute or directive: "
+										+ adname + " from " + value);
+					aliases.add(adname);
+				} else {
+					String advalue = qt.nextToken();
+					clause.put(adname, advalue);
+					del = qt.getSeparator();
+					hadAttribute = true;
+				}
+			}
+			for (Iterator<String> i = aliases.iterator(); i.hasNext();) {
+				String packageName = i.next();
+				if (result.containsKey(packageName)) {
+					if (logger != null)
+						logger
+								.warning("Duplicate package name in header: "
+										+ packageName
+										+ ". Multiple package names in one clause not supported in Bnd.");
+				} else
+					result.put(packageName, clause);
+			}
+		} while (del == ',');
+		return result;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/clauses/Selector.java b/bundleplugin/src/main/java/aQute/libg/clauses/Selector.java
new file mode 100755
index 0000000..eaa806c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/clauses/Selector.java
@@ -0,0 +1,129 @@
+/*
+ * $Header: /cvsroot/bnd/aQute.libg/src/aQute/libg/clauses/Selector.java,v 1.1 2009/01/19 14:17:42 pkriens Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2006). 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.libg.clauses;
+
+import java.util.*;
+import java.util.regex.*;
+
+public class Selector {
+	Pattern	pattern;
+	String	instruction;
+	boolean	negated;
+	Clause	clause;
+
+	public Selector(String instruction, boolean negated) {
+		this.instruction = instruction;
+		this.negated = negated;
+	}
+
+	public boolean matches(String value) {
+		if (pattern == null) {
+			pattern = Pattern.compile(instruction);
+		}
+		Matcher m = pattern.matcher(value);
+		return m.matches();
+	}
+
+	public boolean isNegated() {
+		return negated;
+	}
+
+	public String getPattern() {
+		return instruction;
+	}
+
+	/**
+	 * Convert a string based pattern to a regular expression based pattern.
+	 * This is called an instruction, this object makes it easier to handle the
+	 * different cases
+	 * 
+	 * @param string
+	 * @return
+	 */
+	public static Selector getPattern(String string) {
+		boolean negated = false;
+		if (string.startsWith("!")) {
+			negated = true;
+			string = string.substring(1);
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int c = 0; c < string.length(); c++) {
+			switch (string.charAt(c)) {
+			case '.':
+				sb.append("\\.");
+				break;
+			case '*':
+				sb.append(".*");
+				break;
+			case '?':
+				sb.append(".?");
+				break;
+			default:
+				sb.append(string.charAt(c));
+				break;
+			}
+		}
+		string = sb.toString();
+		if (string.endsWith("\\..*")) {
+			sb.append("|");
+			sb.append(string.substring(0, string.length() - 4));
+		}
+		return new Selector(sb.toString(), negated);
+	}
+
+	public String toString() {
+		return getPattern();
+	}
+
+	public Clause getClause() {
+		return clause;
+	}
+
+	public void setClause(Clause clause) {
+		this.clause = clause;
+	}
+
+	public static List<Selector> getInstructions(Clauses clauses) {
+		List<Selector> result = new ArrayList<Selector>();
+		for (Map.Entry<String, Map<String, String>> entry : clauses.entrySet()) {
+			Selector instruction = getPattern(entry.getKey());
+			result.add(instruction);
+		}
+		return result;
+	}
+	
+	public static <T> List<T> select(Collection<T> domain,
+			List<Selector> instructions) {
+		List<T> result = new ArrayList<T>();
+		Iterator<T> iterator = domain.iterator(); 
+		value: while (iterator.hasNext()) {
+			T value = iterator.next();
+			for (Selector instruction : instructions) {
+				if (instruction.matches(value.toString())) {
+					if (!instruction.isNegated())
+						result.add(value);
+					continue value;
+				}
+			}
+		}
+		return result;
+	}
+	
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/clauses/packageinfo b/bundleplugin/src/main/java/aQute/libg/clauses/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/clauses/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/command/Command.java b/bundleplugin/src/main/java/aQute/libg/command/Command.java
new file mode 100644
index 0000000..0a79a58
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/command/Command.java
@@ -0,0 +1,259 @@
+package aQute.libg.command;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.*;
+
+import aQute.libg.reporter.*;
+
+public class Command {
+
+	boolean				trace;
+	Reporter			reporter;
+	List<String>		arguments	= new ArrayList<String>();
+	Map<String, String>	variables	= new LinkedHashMap<String, String>();
+	long				timeout		= 0;
+	File				cwd			= new File("").getAbsoluteFile();
+	static Timer		timer		= new Timer(Command.class.getName(), true);
+	Process				process;
+	volatile boolean	timedout;
+	String				fullCommand;
+
+	public Command(String fullCommand) {
+		this.fullCommand = fullCommand;
+	}
+
+	public Command() {
+	}
+
+	public int execute(Appendable stdout, Appendable stderr) throws Exception {
+		return execute((InputStream) null, stdout, stderr);
+	}
+
+	public int execute(String input, Appendable stdout, Appendable stderr) throws Exception {
+		InputStream in = new ByteArrayInputStream(input.getBytes("UTF-8"));
+		return execute(in, stdout, stderr);
+	}
+
+	public int execute(InputStream in, Appendable stdout, Appendable stderr) throws Exception {
+		int result;
+		if (reporter != null) {
+			reporter.trace("executing cmd: %s", arguments);
+		}
+
+		String args[] = arguments.toArray(new String[arguments.size()]);
+		String vars[] = new String[variables.size()];
+		int i = 0;
+		for (Entry<String, String> s : variables.entrySet()) {
+			vars[i++] = s.getKey() + "=" + s.getValue();
+		}
+
+		if (fullCommand == null)
+			process = Runtime.getRuntime().exec(args, vars.length == 0 ? null : vars, cwd);
+		else
+			process = Runtime.getRuntime().exec(fullCommand, vars.length == 0 ? null : vars, cwd);
+
+		
+		// Make sure the command will not linger when we go
+		Runnable r = new Runnable() {
+			public void run() {
+				process.destroy();
+			}
+		};
+		Thread hook = new Thread(r, arguments.toString());
+		Runtime.getRuntime().addShutdownHook(hook);
+		TimerTask timer = null;
+		OutputStream stdin = process.getOutputStream();
+		final InputStreamHandler handler = in != null ? new InputStreamHandler(in, stdin) : null;
+
+		if (timeout != 0) {
+			timer = new TimerTask() {
+				public void run() {
+					timedout = true;
+					process.destroy();
+					if (handler != null)
+						handler.interrupt();
+				}
+			};
+			Command.timer.schedule(timer, timeout);
+		}
+
+		InputStream out = process.getInputStream();
+		try {
+			InputStream err = process.getErrorStream();
+			try {
+				new Collector(out, stdout).start();
+				new Collector(err, stderr).start();
+				if (handler != null)
+					handler.start();
+
+				result = process.waitFor();
+				if (reporter != null)
+					reporter.trace("exited process.waitFor, %s", result);
+
+			}
+			finally {
+				err.close();
+			}
+		}
+		finally {
+			out.close();
+			if (timer != null)
+				timer.cancel();
+			Runtime.getRuntime().removeShutdownHook(hook);
+			if (handler != null)
+				handler.interrupt();
+		}
+		if (reporter != null)
+			reporter.trace("cmd %s executed with result=%d, result: %s/%s", arguments, result,
+					stdout, stderr);
+
+		if (timedout)
+			return Integer.MIN_VALUE;
+		byte exitValue = (byte) process.exitValue();
+		return exitValue;
+	}
+
+	public void add(String... args) {
+		for (String arg : args)
+			arguments.add(arg);
+	}
+
+	public void addAll(Collection<String> args) {
+		arguments.addAll(args);
+	}
+
+	public void setTimeout(long duration, TimeUnit unit) {
+		timeout = unit.toMillis(duration);
+	}
+
+	public void setTrace() {
+		this.trace = true;
+	}
+
+	public void setReporter(Reporter reporter) {
+		this.reporter = reporter;
+	}
+
+	public void setCwd(File dir) {
+		if (!dir.isDirectory())
+			throw new IllegalArgumentException("Working directory must be a directory: " + dir);
+
+		this.cwd = dir;
+	}
+
+	public void cancel() {
+		process.destroy();
+	}
+
+	class Collector extends Thread {
+		final InputStream	in;
+		final Appendable	sb;
+
+		Collector(InputStream inputStream, Appendable sb) {
+			this.in = inputStream;
+			this.sb = sb;
+			setDaemon(true);
+		}
+
+		public void run() {
+			try {
+				int c = in.read();
+				while (c >= 0) {
+					sb.append((char) c);
+					c = in.read();
+				}
+			}
+			catch( IOException e) {
+				// We assume the socket is closed
+			}
+			catch (Exception e) {
+				try {
+					sb.append("\n**************************************\n");
+					sb.append(e.toString());
+					sb.append("\n**************************************\n");
+				}
+				catch (IOException e1) {
+				}
+				if (reporter != null) {
+					reporter.trace("cmd exec: %s", e);
+				}
+			}
+		}
+	}
+
+	static class InputStreamHandler extends Thread {
+		final InputStream	in;
+		final OutputStream	stdin;
+
+		InputStreamHandler(InputStream in, OutputStream stdin) {
+			this.stdin = stdin;
+			this.in = in;
+			setDaemon(true);
+		}
+
+		public void run() {
+			try {
+				int c = in.read();
+				while (c >= 0) {
+					stdin.write(c);
+					stdin.flush();
+					c = in.read();
+				}
+			}
+			catch (InterruptedIOException e) {
+				// Ignore here
+			}
+			catch (Exception e) {
+				// Who cares?
+			}
+			finally {
+				try {
+					stdin.close();
+				}
+				catch (IOException e) {
+					// Who cares?
+				}
+			}
+		}
+	}
+
+	public Command var(String name, String value) {
+		variables.put(name, value);
+		return this;
+	}
+
+	public Command arg(String... args) {
+		add(args);
+		return this;
+	}
+
+	public Command full(String full) {
+		fullCommand = full;
+		return this;
+	}
+
+	public void inherit() {
+		ProcessBuilder pb = new ProcessBuilder();
+		for (Entry<String, String> e : pb.environment().entrySet()) {
+			var(e.getKey(), e.getValue());
+		}
+	}
+
+	public String var(String name) {
+		return variables.get(name);
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		String del = "";
+
+		for (String argument : arguments) {
+			sb.append(del);
+			sb.append(argument);
+			del = " ";
+		}
+		return sb.toString();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/command/packageinfo b/bundleplugin/src/main/java/aQute/libg/command/packageinfo
new file mode 100644
index 0000000..a2afe57
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/command/packageinfo
@@ -0,0 +1 @@
+version 2.1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/Crypto.java b/bundleplugin/src/main/java/aQute/libg/cryptography/Crypto.java
new file mode 100644
index 0000000..ff971e8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/Crypto.java
@@ -0,0 +1,67 @@
+package aQute.libg.cryptography;
+
+import java.math.*;
+import java.security.*;
+import java.security.interfaces.*;
+import java.security.spec.*;
+import java.util.regex.*;
+
+public class Crypto {
+	static final Pattern	RSA_PRIVATE	= Pattern
+												.compile("\\s*RSA\\.Private\\((\\p{XDigit})+:(\\p{XDigit})+\\)\\s*");
+	static final Pattern	RSA_PUBLIC	= Pattern
+												.compile("\\s*RSA\\.Public\\((\\p{XDigit})+:(\\p{XDigit})+\\)\\s*");
+
+	/**
+	 * 
+	 * @param <T>
+	 * @param spec
+	 * @return
+	 * @throws Exception
+	 */
+	@SuppressWarnings("unchecked") public static <T> T fromString(String spec, Class<T> c) throws Exception {
+		if ( PrivateKey.class.isAssignableFrom(c)) {
+			Matcher m = RSA_PRIVATE.matcher(spec); 
+			if ( m.matches()) {
+				return (T) RSA.createPrivate(  
+						new BigInteger(m.group(1)), new BigInteger(m.group(2)));
+			}
+			throw new IllegalArgumentException("No such private key " + spec );
+		}
+		
+		if ( PublicKey.class.isAssignableFrom(c)) {
+			Matcher m = RSA_PUBLIC.matcher(spec); 
+			if ( m.matches()) {
+				return (T) RSA.create( new RSAPublicKeySpec( 
+						new BigInteger(m.group(1)), new BigInteger(m.group(2))));
+			}
+			throw new IllegalArgumentException("No such public key " + spec );			
+		}
+		return null;
+	}
+
+	public static String toString( Object key ) {
+		if ( key instanceof RSAPrivateKey ) {
+			RSAPrivateKey pk = (RSAPrivateKey) key;
+			return "RSA.Private(" + pk.getModulus() + ":" + pk.getPrivateExponent() + ")";
+		}
+		if ( key instanceof RSAPublicKey ) {
+			RSAPublicKey pk = (RSAPublicKey) key;
+			return "RSA.Private(" + pk.getModulus() + ":" + pk.getPublicExponent() + ")";
+		}
+		return null;
+	}
+
+
+//	public static <T extends Digest> Signer<T> signer(PrivateKey key, Digester<T> digester) throws NoSuchAlgorithmException {
+//		Signature s = Signature.getInstance(key.getAlgorithm() + "with" + digester.getAlgorithm());
+//		return new Signer<T>(s,digester);
+//	}
+
+	public static Verifier verifier(PublicKey key, Digest digest) throws NoSuchAlgorithmException {
+		Signature s = Signature.getInstance(key.getAlgorithm() + "with" + digest.getAlgorithm());
+		return new Verifier(s,digest);
+	}
+
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/Digest.java b/bundleplugin/src/main/java/aQute/libg/cryptography/Digest.java
new file mode 100644
index 0000000..90e7b1a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/Digest.java
@@ -0,0 +1,44 @@
+package aQute.libg.cryptography;
+
+import java.util.*;
+
+import aQute.lib.hex.*;
+
+public abstract class Digest {
+	final byte[]	digest;
+
+	protected Digest(byte[] checksum, int width) {
+		this.digest = checksum;
+		if (digest.length != width)
+			throw new IllegalArgumentException("Invalid width for digest: " + digest.length
+					+ " expected " + width);
+	}
+
+
+	public byte[] digest() {
+		return digest;
+	}
+
+	@Override public String toString() {
+		return String.format("%s(d=%s)", getAlgorithm(), Hex.toHexString(digest));
+	}
+
+	public abstract String getAlgorithm();
+	
+	
+	public boolean equals(Object other) {
+		if ( !(other instanceof Digest))
+			return false;
+
+		Digest d = (Digest) other;
+		return Arrays.equals(d.digest, digest);
+	}
+	
+	public int hashCode() {
+		return Arrays.hashCode(digest);
+	}
+
+	public byte[] toByteArray() {
+		return digest();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java b/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java
new file mode 100644
index 0000000..eeb2c8f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java
@@ -0,0 +1,47 @@
+package aQute.libg.cryptography;
+
+import java.io.*;
+import java.security.*;
+
+import aQute.lib.io.*;
+
+public abstract class Digester<T extends Digest> extends OutputStream {
+	protected MessageDigest	md;
+	OutputStream out[];
+	
+	public Digester(MessageDigest instance, OutputStream ... out) {
+		md = instance;
+		this.out = out;
+	}
+	
+	@Override
+	public void write( byte[] buffer, int offset, int length) throws IOException{
+		md.update(buffer,offset,length);
+		for ( OutputStream o : out ) {
+			o.write(buffer, offset, length);
+		}
+	}
+	@Override
+	public void write( int b) throws IOException{
+		md.update((byte) b);
+		for ( OutputStream o : out ) {
+			o.write(b);
+		}
+	}
+	
+	public MessageDigest getMessageDigest() throws Exception {
+		return md;
+	}
+	
+	public T from(InputStream in) throws Exception {
+		IO.copy(in,this);
+		return digest();
+	}
+		
+	public void setOutputs(OutputStream ...out) {
+		this.out = out;
+	}
+	public abstract T digest() throws Exception;
+	public abstract T digest( byte [] bytes) throws Exception;
+	public abstract String getAlgorithm();
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/Key.java b/bundleplugin/src/main/java/aQute/libg/cryptography/Key.java
new file mode 100644
index 0000000..160ec05
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/Key.java
@@ -0,0 +1,8 @@
+package aQute.libg.cryptography;
+
+
+public abstract class Key implements java.security.Key {
+	private static final long	serialVersionUID	= 1L;
+
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java b/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java
new file mode 100644
index 0000000..84b53ee
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java
@@ -0,0 +1,34 @@
+package aQute.libg.cryptography;
+
+import java.io.*;
+import java.security.*;
+
+
+
+public class MD5 extends Digest {
+	public final static String ALGORITHM = "MD5";
+	
+	public static Digester<MD5> getDigester(OutputStream ... out) throws Exception {
+		return new Digester<MD5>(MessageDigest.getInstance(ALGORITHM), out) {
+			
+			@Override public MD5 digest() throws Exception {
+				return new MD5(md.digest());
+			}
+
+			@Override public MD5 digest(byte[] bytes) {
+				return new MD5(bytes);
+			}
+			@Override public String getAlgorithm() {
+				return ALGORITHM;
+			}
+		};
+	}
+	
+	
+	public MD5(byte[] digest) {
+		super(digest,16);
+	}
+
+	@Override public String getAlgorithm() { return ALGORITHM; }
+
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/RSA.java b/bundleplugin/src/main/java/aQute/libg/cryptography/RSA.java
new file mode 100644
index 0000000..61bafb5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/RSA.java
@@ -0,0 +1,43 @@
+package aQute.libg.cryptography;
+
+import java.math.*;
+import java.security.*;
+import java.security.interfaces.*;
+import java.security.spec.*;
+
+import aQute.libg.tuple.*;
+
+public class RSA {
+	final static String		ALGORITHM	= "RSA";
+
+	final static KeyFactory	factory		= getKeyFactory();
+
+	static private KeyFactory getKeyFactory() {
+		try {
+			return KeyFactory.getInstance(ALGORITHM);
+		} catch (Exception e) {
+			// built in
+		}
+		return null;
+	}
+
+	public static RSAPrivateKey create(RSAPrivateKeySpec keyspec) throws InvalidKeySpecException {
+		return (RSAPrivateKey) factory.generatePrivate(keyspec);
+	}
+	public static RSAPublicKey create(RSAPublicKeySpec keyspec) throws InvalidKeySpecException {
+		return (RSAPublicKey) factory.generatePrivate(keyspec);
+	}
+	
+	public static RSAPublicKey createPublic(BigInteger m, BigInteger e) throws InvalidKeySpecException {
+		return create( new RSAPublicKeySpec(m,e));
+	}
+	public static RSAPrivateKey createPrivate(BigInteger m, BigInteger e) throws InvalidKeySpecException {
+		return create( new RSAPrivateKeySpec(m,e));
+	}
+	
+	public static Pair<RSAPrivateKey, RSAPublicKey> generate() throws NoSuchAlgorithmException {
+		KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM);
+		KeyPair keypair = kpg.generateKeyPair();
+		return new Pair<RSAPrivateKey,RSAPublicKey>( (RSAPrivateKey) keypair.getPrivate(), (RSAPublicKey) keypair.getPublic());
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java
new file mode 100644
index 0000000..a01f112
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java
@@ -0,0 +1,35 @@
+package aQute.libg.cryptography;
+
+import java.io.*;
+import java.security.*;
+
+
+
+public class SHA1 extends Digest {
+	public final static String ALGORITHM = "SHA1";
+	
+	
+	public static Digester<SHA1> getDigester(OutputStream ... out  ) throws NoSuchAlgorithmException {
+		MessageDigest md = MessageDigest.getInstance(ALGORITHM);
+		return new Digester<SHA1>(md, out) {
+			@Override public SHA1 digest() throws Exception {
+				return new SHA1(md.digest());
+			}
+
+			@Override public SHA1 digest(byte[] bytes) {
+				return new SHA1(bytes);
+			}
+			@Override public String getAlgorithm() {
+				return ALGORITHM;
+			}
+		};
+	}
+	
+	public SHA1(byte[] b) {
+		super(b, 20);
+	}
+
+
+	@Override public String getAlgorithm() { return ALGORITHM; }
+
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java
new file mode 100644
index 0000000..5b25cf3
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java
@@ -0,0 +1,35 @@
+package aQute.libg.cryptography;
+
+import java.io.*;
+import java.security.*;
+
+
+
+public class SHA256 extends Digest {
+	public final static String ALGORITHM = "SHA256";
+	
+	
+	public static Digester<SHA256> getDigester(OutputStream ... out  ) throws NoSuchAlgorithmException {
+		MessageDigest md = MessageDigest.getInstance(ALGORITHM);
+		return new Digester<SHA256>(md, out) {
+			@Override public SHA256 digest() throws Exception {
+				return new SHA256(md.digest());
+			}
+
+			@Override public SHA256 digest(byte[] bytes) {
+				return new SHA256(bytes);
+			}
+			@Override public String getAlgorithm() {
+				return ALGORITHM;
+			}
+		};
+	}
+	
+	public SHA256(byte[] b) {
+		super(b, 32);
+	}
+
+
+	@Override public String getAlgorithm() { return ALGORITHM; }
+
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/Signer.java b/bundleplugin/src/main/java/aQute/libg/cryptography/Signer.java
new file mode 100644
index 0000000..5218e2d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/Signer.java
@@ -0,0 +1,41 @@
+package aQute.libg.cryptography;
+
+import java.io.*;
+import java.security.*;
+
+public class Signer<D extends Digest> extends OutputStream {
+	Signature	signature;
+	Digester<D> digester;
+	
+	Signer(Signature s, Digester<D> digester) {
+		this.signature = s;
+		this.digester  = digester;
+	}
+
+	@Override public void write(byte[] buffer, int offset, int length) throws IOException {
+		try {
+			signature.update(buffer, offset, length);
+			digester.write(buffer, offset, length);
+		} catch (SignatureException e) {
+			throw new IOException(e.getLocalizedMessage());
+		}
+	}
+
+	@Override public void write(int b) throws IOException {
+		try {
+			signature.update((byte) b);
+			digester.write(b);
+		} catch (SignatureException e) {
+			throw new IOException(e.getLocalizedMessage());
+		}
+	}
+	
+
+	public Signature signature() throws Exception {
+		return signature;
+	}
+	
+	public D digest() throws Exception {
+		return digester.digest();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/Verifier.java b/bundleplugin/src/main/java/aQute/libg/cryptography/Verifier.java
new file mode 100644
index 0000000..5506ece
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/Verifier.java
@@ -0,0 +1,38 @@
+package aQute.libg.cryptography;
+
+import java.io.*;
+import java.security.*;
+
+
+public class Verifier extends OutputStream {
+	final Signature signature;
+	final Digest d;
+	
+	Verifier(Signature s, Digest d) {
+		this.signature = s;
+		this.d = d;
+	}
+	
+	@Override
+	public void write( byte[] buffer, int offset, int length) throws IOException {
+		try {
+			signature.update(buffer, offset, length);
+		} catch (SignatureException e) {
+			throw new IOException(e.getLocalizedMessage());
+		}
+	}
+
+	@Override
+	public void write( int b) throws IOException {
+		try {
+			signature.update((byte) b);
+		} catch (SignatureException e) {
+			throw new IOException(e.getLocalizedMessage());
+		}
+	}
+
+	public boolean verify() throws Exception {
+		return signature.verify(d.digest());
+	}
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/packageinfo b/bundleplugin/src/main/java/aQute/libg/cryptography/packageinfo
new file mode 100644
index 0000000..e39f616
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/packageinfo
@@ -0,0 +1 @@
+version 1.1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/fileiterator/FileIterator.java b/bundleplugin/src/main/java/aQute/libg/fileiterator/FileIterator.java
new file mode 100644
index 0000000..7cd73a2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/fileiterator/FileIterator.java
@@ -0,0 +1,43 @@
+package aQute.libg.fileiterator;
+
+import java.io.*;
+import java.util.*;
+
+public class FileIterator implements Iterator<File> {
+    File         dir;
+    int          n = 0;
+    FileIterator next;
+
+    public FileIterator(File nxt) {
+        assert nxt.isDirectory();
+        this.dir = nxt;
+    }
+
+    public boolean hasNext() {
+        if (next != null)
+            return next.hasNext();
+		return n < dir.list().length;
+    }
+
+    public File next() {
+        if (next != null) {
+            File answer = next.next();
+            if (!next.hasNext())
+                next = null;
+            return answer;
+        }
+		File nxt = dir.listFiles()[n++];
+		if (nxt.isDirectory()) {
+		    next = new FileIterator(nxt);
+		    return nxt;
+		} else if (nxt.isFile()) {
+		    return nxt;
+		} else
+		    throw new IllegalStateException("File disappeared");
+    }
+
+    public void remove() {
+        throw new UnsupportedOperationException(
+                "Cannot remove from a file iterator");
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/fileiterator/packageinfo b/bundleplugin/src/main/java/aQute/libg/fileiterator/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/fileiterator/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/filelock/DirectoryLock.java b/bundleplugin/src/main/java/aQute/libg/filelock/DirectoryLock.java
new file mode 100644
index 0000000..6e6b11e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/filelock/DirectoryLock.java
@@ -0,0 +1,32 @@
+package aQute.libg.filelock;
+
+import java.io.*;
+
+public class DirectoryLock {
+	final File						lock;
+	final long						timeout;
+	final public static String LOCKNAME = ".lock";
+
+	public DirectoryLock(File directory, long timeout) {
+		this.lock = new File(directory, LOCKNAME);
+		this.lock.deleteOnExit();
+		this.timeout = timeout;
+	}
+
+	
+	public void release() {
+		lock.delete();
+	}
+
+	public void lock() throws InterruptedException {
+		if (lock.mkdir())
+			return;
+
+		long deadline = System.currentTimeMillis()+ timeout;
+		while ( System.currentTimeMillis() < deadline) {
+			if (lock.mkdir())
+				return;	
+			Thread.sleep(50);
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/filelock/packageinfo b/bundleplugin/src/main/java/aQute/libg/filelock/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/filelock/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
diff --git a/bundleplugin/src/main/java/aQute/libg/filerepo/FileRepo.java b/bundleplugin/src/main/java/aQute/libg/filerepo/FileRepo.java
new file mode 100644
index 0000000..a0149bc
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/filerepo/FileRepo.java
@@ -0,0 +1,106 @@
+package aQute.libg.filerepo;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.libg.version.*;
+
+public class FileRepo {
+	File	root;
+	Pattern	REPO_FILE	= Pattern.compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+|latest)\\.(jar|lib)");
+
+	public FileRepo(File root) {
+		this.root = root;
+	}
+
+	/**
+	 * Get a list of URLs to bundles that are constrained by the bsn and
+	 * versionRange.
+	 */
+	public File[] get(String bsn, final VersionRange versionRange) throws Exception {
+
+		//
+		// Check if the entry exists
+		//
+		File f = new File(root, bsn);
+		if (!f.isDirectory())
+			return null;
+
+		//
+		// Iterator over all the versions for this BSN.
+		// Create a sorted map over the version as key
+		// and the file as URL as value. Only versions
+		// that match the desired range are included in
+		// this list.
+		//
+		return f.listFiles(new FilenameFilter() {
+			public boolean accept(File dir, String name) {
+				Matcher m = REPO_FILE.matcher(name);
+				if (!m.matches())
+					return false;
+				if ( versionRange == null)
+					return true;
+				
+				Version v = new Version(m.group(2));
+				return versionRange.includes(v);
+			}
+		});
+	}
+
+	public List<String> list(String regex) throws Exception {
+		if (regex == null)
+			regex = ".*";
+		final Pattern pattern = Pattern.compile(regex);
+
+		String list[] = root.list(new FilenameFilter() {
+
+			public boolean accept(File dir, String name) {
+				Matcher matcher = pattern.matcher(name);
+				return matcher.matches();
+			}
+
+		});
+		return Arrays.asList(list);
+	}
+
+	public List<Version> versions(String bsn) throws Exception {
+		File dir = new File(root, bsn);
+		final List<Version> versions = new ArrayList<Version>();
+		dir.list(new FilenameFilter() {
+
+			public boolean accept(File dir, String name) {
+				Matcher m = REPO_FILE.matcher(name);
+				if (m.matches()) {
+					versions.add(new Version(m.group(2)));
+					return true;
+				}
+				return false;
+			}
+
+		});
+		return versions;
+	}
+
+	public File get(String bsn, VersionRange range, int strategy) throws Exception {
+		File[] files = get(bsn, range);
+		if (files == null || files.length == 0)
+			return null;
+
+		if (files.length == 1)
+			return files[0];
+
+		if (strategy < 0) {
+			return files[0];
+		}
+		return files[files.length - 1];
+	}
+
+	public File put(String bsn, Version version) {
+		File dir = new File(root,bsn);
+		dir.mkdirs();
+		File file = new File(dir, bsn + "-" + version.getMajor() + "." + version.getMinor() + "." + version.getMicro() + ".jar");
+		return file;
+	}
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/filerepo/packageinfo b/bundleplugin/src/main/java/aQute/libg/filerepo/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/filerepo/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/forker/Forker.java b/bundleplugin/src/main/java/aQute/libg/forker/Forker.java
new file mode 100644
index 0000000..2559fad
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/forker/Forker.java
@@ -0,0 +1,212 @@
+package aQute.libg.forker;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+/**
+ * A Forker is good in parallel scheduling tasks with dependencies. You can add
+ * tasks with {@link #doWhen(Collection, Object, Runnable)}. The collection is
+ * the list of dependencies, the object is the target, and the runnable is run
+ * to update the target. The runnable will only run when all its dependencies
+ * have ran their associated runnable.
+ * 
+ * @author aqute
+ * 
+ * @param <T>
+ */
+public class Forker<T> {
+	final Executor		executor;
+	final Map<T, Job>	waiting		= new HashMap<T, Job>();
+	final Set<Job>		executing	= new HashSet<Job>();
+	final AtomicBoolean	canceled	= new AtomicBoolean();
+	private int			count;
+
+	/**
+	 * Helper class to model a Job
+	 */
+	class Job implements Runnable {
+		T						target;
+		Set<T>					dependencies;
+		Runnable				runnable;
+		Throwable				exception;
+		volatile Thread			t;
+		volatile AtomicBoolean	canceled	= new AtomicBoolean(false);
+
+		/**
+		 * Run when the job's dependencies are done.
+		 */
+		public void run() {
+			Thread.interrupted(); // clear the interrupt flag
+
+			try {
+				synchronized (this) {
+					// Check if we got canceled
+					if (canceled.get())
+						return;
+
+					t = Thread.currentThread();
+				}
+				runnable.run();
+			} catch (Exception e) {
+				exception = e;
+				e.printStackTrace();
+			} finally {
+				synchronized (this) {
+					t = null;
+				}
+				Thread.interrupted(); // clear the interrupt flag
+				done(this);
+			}
+		}
+
+		/**
+		 * Cancel this job
+		 */
+		private void cancel() {
+			if (!canceled.getAndSet(true)) {
+				synchronized (this) {
+					if (t != null)
+						t.interrupt();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Constructor
+	 * 
+	 * @param executor
+	 */
+	public Forker(Executor executor) {
+		this.executor = executor;
+	}
+
+	/**
+	 * Constructor
+	 * 
+	 */
+	public Forker() {
+		this.executor = Executors.newFixedThreadPool(4);
+	}
+
+	/**
+	 * Schedule a job for execution when the dependencies are done of target are
+	 * done.
+	 * 
+	 * @param dependencies
+	 *            the dependencies that must have run
+	 * @param target
+	 *            the target, is removed from all the dependencies when it ran
+	 * @param runnable
+	 *            the runnable to run
+	 */
+	public synchronized void doWhen(Collection<? extends T> dependencies, T target,
+			Runnable runnable) {
+		if (waiting.containsKey(target))
+			throw new IllegalArgumentException("You can only add a target once to the forker");
+
+		System.err.println("doWhen " + dependencies + " " + target);
+		Job job = new Job();
+		job.dependencies = new HashSet<T>(dependencies);
+		job.target = target;
+		job.runnable = runnable;
+		waiting.put(target, job);
+	}
+
+	public void start(long ms) throws InterruptedException {
+		check();
+		count = waiting.size();
+		System.err.println("Count " + count);
+		schedule();
+		if (ms >= 0)
+			sync(ms);
+	}
+
+	private void check() {
+		Set<T> dependencies = new HashSet<T>();
+		for (Job job : waiting.values())
+			dependencies.addAll(job.dependencies);
+		dependencies.removeAll(waiting.keySet());
+		if (dependencies.size() > 0)
+			throw new IllegalArgumentException(
+					"There are dependencies in the jobs that are not present in the targets: "
+							+ dependencies);
+
+	}
+
+	public synchronized void sync(long ms) throws InterruptedException {
+		System.err.println("Waiting for sync");
+		while (count > 0) {
+			System.err.println("Waiting for sync " + count);
+			wait(ms);
+		}
+		System.err.println("Exiting sync " + count);
+	}
+
+	private void schedule() {
+		if (canceled.get())
+			return;
+
+		List<Runnable> torun = new ArrayList<Runnable>();
+		synchronized (this) {
+			for (Iterator<Job> e = waiting.values().iterator(); e.hasNext();) {
+				Job job = e.next();
+				if (job.dependencies.isEmpty()) {
+					torun.add(job);
+					executing.add(job);
+					e.remove();
+				}
+			}
+		}
+		for (Runnable r : torun)
+			executor.execute(r);
+	}
+
+	/**
+	 * Called when the target has ran by the Job.
+	 * 
+	 * @param done
+	 */
+	private void done(Job done) {
+		synchronized (this) {
+			System.err.println("count = " + count);
+			executing.remove(done);
+			count--;
+			if (count == 0) {
+				System.err.println("finished");
+				notifyAll();
+				return;
+			}
+
+			for (Job job : waiting.values()) {
+				// boolean x =
+					job.dependencies.remove(done.target);
+				//System.err.println( "Removing " + done.target + " from " + job.target + " ?" + x  + " " + job.dependencies.size());
+			}
+		}
+		schedule();
+	}
+
+	/**
+	 * Cancel the forker.
+	 * 
+	 * @throws InterruptedException
+	 */
+	public void cancel(long ms) throws InterruptedException {
+		System.err.println("canceled " + count);
+
+		if (!canceled.getAndSet(true)) {
+			synchronized (this) {
+				for (Job job : executing) {
+					job.cancel();
+				}
+			}
+		}
+		sync(ms);
+	}
+
+	public int getCount() {
+		return count;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/forker/packageinfo b/bundleplugin/src/main/java/aQute/libg/forker/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/forker/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/generics/Create.java b/bundleplugin/src/main/java/aQute/libg/generics/Create.java
new file mode 100644
index 0000000..ee708fd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/generics/Create.java
@@ -0,0 +1,54 @@
+package aQute.libg.generics;
+
+import java.util.*;
+
+public class Create {
+    
+    public static <K,V>  Map<K, V> map() {
+        return new LinkedHashMap<K,V>();
+    }
+
+    public static <K,V>  Map<K, V> map(Class<K> key, Class<V> value) {
+        return Collections.checkedMap(new LinkedHashMap<K,V>(), key,value);
+    }
+
+    public static <T>  List<T> list() {
+        return new ArrayList<T>();
+    }
+
+    public static <T>  List<T> list(Class<T> c) {
+        return Collections.checkedList(new ArrayList<T>(),c) ;
+    }
+
+    public static <T>  Set<T> set() {
+        return new HashSet<T>();
+    }
+
+    public static <T>  Set<T> set(Class<T> c) {
+        return Collections.checkedSet(new HashSet<T>(),c);
+    }
+
+    public static <T>  List<T> list(T[] source) {
+        return new ArrayList<T>(Arrays.asList(source));
+    }
+
+    public static <T>  Set<T> set(T[]source) {
+        return new HashSet<T>(Arrays.asList(source));
+    }
+
+    public static <K,V>  Map<K, V> copy(Map<K,V> source) {
+        return new LinkedHashMap<K,V>(source);
+    }
+
+    public static <T>  List<T> copy(List<T> source) {
+        return new ArrayList<T>(source);
+    }
+
+    public static <T>  Set<T> copy(Collection<T> source) {
+        if ( source == null )
+            return set();
+        return new HashSet<T>(source);
+    }
+
+    
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/generics/packageinfo b/bundleplugin/src/main/java/aQute/libg/generics/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/generics/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/glob/Glob.java b/bundleplugin/src/main/java/aQute/libg/glob/Glob.java
new file mode 100644
index 0000000..c4d06a7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/glob/Glob.java
@@ -0,0 +1,110 @@
+package aQute.libg.glob;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Glob {
+
+	private final String glob;
+	private final Pattern pattern;
+	
+	public Glob(String globString) {
+		this.glob = globString;
+		this.pattern = Pattern.compile(convertGlobToRegEx(globString));
+	}
+	
+	public Matcher matcher(CharSequence input) {
+		return pattern.matcher(input);
+	}
+	
+	@Override
+	public String toString() {
+		return glob;
+	}
+
+	private static String convertGlobToRegEx(String line) {
+		line = line.trim();
+		int strLen = line.length();
+		StringBuilder sb = new StringBuilder(strLen);
+		// Remove beginning and ending * globs because they're useless
+		if (line.startsWith("*")) {
+			line = line.substring(1);
+			strLen--;
+		}
+		if (line.endsWith("*")) {
+			line = line.substring(0, strLen - 1);
+			strLen--;
+		}
+		boolean escaping = false;
+		int inCurlies = 0;
+		for (char currentChar : line.toCharArray()) {
+			switch (currentChar) {
+			case '*':
+				if (escaping)
+					sb.append("\\*");
+				else
+					sb.append(".*");
+				escaping = false;
+				break;
+			case '?':
+				if (escaping)
+					sb.append("\\?");
+				else
+					sb.append('.');
+				escaping = false;
+				break;
+			case '.':
+			case '(':
+			case ')':
+			case '+':
+			case '|':
+			case '^':
+			case '$':
+			case '@':
+			case '%':
+				sb.append('\\');
+				sb.append(currentChar);
+				escaping = false;
+				break;
+			case '\\':
+				if (escaping) {
+					sb.append("\\\\");
+					escaping = false;
+				} else
+					escaping = true;
+				break;
+			case '{':
+				if (escaping) {
+					sb.append("\\{");
+				} else {
+					sb.append('(');
+					inCurlies++;
+				}
+				escaping = false;
+				break;
+			case '}':
+				if (inCurlies > 0 && !escaping) {
+					sb.append(')');
+					inCurlies--;
+				} else if (escaping)
+					sb.append("\\}");
+				else
+					sb.append("}");
+				escaping = false;
+				break;
+			case ',':
+				if (inCurlies > 0 && !escaping) {
+					sb.append('|');
+				} else if (escaping)
+					sb.append("\\,");
+				else
+					sb.append(",");
+				break;
+			default:
+				escaping = false;
+				sb.append(currentChar);
+			}
+		}
+		return sb.toString();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/glob/packageinfo b/bundleplugin/src/main/java/aQute/libg/glob/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/glob/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
diff --git a/bundleplugin/src/main/java/aQute/libg/gzip/GZipUtils.java b/bundleplugin/src/main/java/aQute/libg/gzip/GZipUtils.java
new file mode 100644
index 0000000..6606d0e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/gzip/GZipUtils.java
@@ -0,0 +1,60 @@
+package aQute.libg.gzip;
+
+import java.io.BufferedInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+public class GZipUtils {
+	
+	/**
+	 * Determines whether the specified stream contains gzipped data, by
+	 * checking for the GZIP magic number, and returns a stream capable of
+	 * reading those data.
+	 * 
+	 * @throws IOException
+	 */
+	public static InputStream detectCompression(InputStream stream) throws IOException {
+		InputStream buffered;
+		if (stream.markSupported())
+			buffered = stream;
+		else
+			buffered = new BufferedInputStream(stream);
+		
+		buffered.mark(2);
+		int magic = readUShort(buffered);
+		buffered.reset();
+		
+		InputStream result;
+		if (magic == GZIPInputStream.GZIP_MAGIC)
+			result = new GZIPInputStream(buffered);
+		else
+			result = buffered;
+		return result;
+	}
+
+    /*
+     * Reads unsigned short in Intel byte order.
+     */
+	private static int readUShort(InputStream in) throws IOException {
+		int b = readUByte(in);
+		return (readUByte(in) << 8) | b;
+	}
+    
+    /*
+     * Reads unsigned byte.
+     */
+    private static int readUByte(InputStream in) throws IOException {
+	int b = in.read();
+	if (b == -1) {
+	    throw new EOFException();
+	}
+        if (b < -1 || b > 255) {
+            // Report on this.in, not argument in; see read{Header, Trailer}.
+            throw new IOException(in.getClass().getName() + ".read() returned value out of range -1..255: " + b);
+        }
+	return b;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/gzip/packageinfo b/bundleplugin/src/main/java/aQute/libg/gzip/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/gzip/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
diff --git a/bundleplugin/src/main/java/aQute/libg/header/Attrs.java b/bundleplugin/src/main/java/aQute/libg/header/Attrs.java
new file mode 100644
index 0000000..d11b6d1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/header/Attrs.java
@@ -0,0 +1,295 @@
+package aQute.libg.header;
+
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.lib.collections.*;
+import aQute.libg.version.*;
+
+public class Attrs implements Map<String, String> {
+	public enum Type {
+		STRING(null), LONG(null), VERSION(null), DOUBLE(null), STRINGS(STRING), LONGS(LONG), VERSIONS(VERSION), DOUBLES(DOUBLE);
+
+		Type	sub;
+
+		Type(Type sub) {
+			this.sub = sub;
+		}
+
+	}
+
+	/**
+	 * <pre>
+	 * Provide-Capability ::= capability ::=
+	 * name-space ::= typed-attr ::= type ::= scalar ::=
+	 * capability ( ',' capability )*
+	 * name-space
+	 *     ( ’;’ directive | typed-attr )*
+	 * symbolic-name
+	 * extended ( ’:’ type ) ’=’ argument
+	 * scalar | list
+	 * ’String’ | ’Version’ | ’Long’
+	 * list ::=
+	 * ’List<’ scalar ’>’
+	 * </pre>
+	 */
+	static String					EXTENDED	= "[\\-0-9a-zA-Z\\._]+";
+	static String					SCALAR		= "String|Version|Long|Double";
+	static String					LIST		= "List\\s*<\\s*(" + SCALAR + ")\\s*>";
+	public static final Pattern		TYPED		= Pattern.compile("\\s*(" + EXTENDED + ")\\s*:\\s*("+ SCALAR + "|" + LIST + ")\\s*");
+
+	private LinkedHashMap<String, String>	map;
+	private Map<String, Type>		types;
+	static Map<String, String>		EMPTY		= Collections.emptyMap();
+
+	public Attrs(Attrs... attrs) {
+		for (Attrs a : attrs) {
+			if (a != null) {
+				putAll(a);
+			}
+		}
+	}
+
+	public void clear() {
+		map.clear();
+	}
+
+	public boolean containsKey(String name) {
+		if (map == null)
+			return false;
+
+		return map.containsKey(name);
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public boolean containsKey(Object name) {
+		assert name instanceof String;
+		if (map == null)
+			return false;
+
+		return map.containsKey((String) name);
+	}
+
+	public boolean containsValue(String value) {
+		if (map == null)
+			return false;
+
+		return map.containsValue(value);
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public boolean containsValue(Object value) {
+		assert value instanceof String;
+		if (map == null)
+			return false;
+
+		return map.containsValue((String) value);
+	}
+
+	public Set<java.util.Map.Entry<String, String>> entrySet() {
+		if (map == null)
+			return EMPTY.entrySet();
+
+		return map.entrySet();
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public String get(Object key) {
+		assert key instanceof String;
+		if (map == null)
+			return null;
+
+		return map.get((String) key);
+	}
+
+	public String get(String key) {
+		if (map == null)
+			return null;
+
+		return map.get(key);
+	}
+
+	public String get(String key, String deflt) {
+		String s = get(key);
+		if (s == null)
+			return deflt;
+		return s;
+	}
+
+	public boolean isEmpty() {
+		return map == null || map.isEmpty();
+	}
+
+	public Set<String> keySet() {
+		if (map == null)
+			return EMPTY.keySet();
+
+		return map.keySet();
+	}
+
+	public String put(String key, String value) {
+		if (map == null)
+			map = new LinkedHashMap<String, String>();
+
+		Matcher m = TYPED.matcher(key);
+		if (m.matches()) {
+			key = m.group(1);
+			String type = m.group(2);
+			Type t = Type.STRING;
+
+			if ( type.startsWith("List")) {
+				type = m.group(3);
+				if ( "String".equals(type))
+					t = Type.STRINGS;
+				else if ( "Long".equals(type))
+					t = Type.LONGS;
+				else if ( "Double".equals(type))
+					t = Type.DOUBLES;
+				else if ( "Version".equals(type))
+					t = Type.VERSIONS;				
+			} else {
+				if ( "String".equals(type))
+					t = Type.STRING;
+				else if ( "Long".equals(type))
+					t = Type.LONG;
+				else if ( "Double".equals(type))
+					t = Type.DOUBLE;
+				else if ( "Version".equals(type))
+					t = Type.VERSION;
+			}
+			if (types == null)
+				types = new LinkedHashMap<String, Type>();
+			types.put(key, t);
+
+			// TODO verify value?
+		}
+
+		return map.put(key, value);
+	}
+
+	public Type getType(String key) {
+		if (types == null)
+			return Type.STRING;
+		Type t = types.get(key);
+		if (t == null)
+			return Type.STRING;
+		return t;
+	}
+
+	public void putAll(Map<? extends String, ? extends String> map) {
+		for (Map.Entry<? extends String, ? extends String> e : map.entrySet())
+			put(e.getKey(), e.getValue());
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public String remove(Object var0) {
+		assert var0 instanceof String;
+		if (map == null)
+			return null;
+
+		return map.remove((String) var0);
+	}
+
+	public String remove(String var0) {
+		if (map == null)
+			return null;
+		return map.remove(var0);
+	}
+
+	public int size() {
+		if (map == null)
+			return 0;
+		return map.size();
+	}
+
+	public Collection<String> values() {
+		if (map == null)
+			return EMPTY.values();
+
+		return map.values();
+	}
+
+	public String getVersion() {
+		return get("version");
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		append(sb);
+		return sb.toString();
+	}
+
+	public void append(StringBuilder sb) {
+		String del = "";
+		for (Map.Entry<String, String> e : entrySet()) {
+			sb.append(del);
+			sb.append(e.getKey());
+			sb.append("=");
+			sb.append(e.getValue());
+			del = ";";
+		}
+	}
+
+	@Deprecated public boolean equals(Object other) {
+		return super.equals(other);
+	}
+
+	@Deprecated public int hashCode() {
+		return super.hashCode();
+	}
+
+	public boolean isEqual(Attrs o) {
+		if (this == o)
+			return true;
+
+		Attrs other = o;
+
+		if (size() != other.size())
+			return false;
+
+		if (isEmpty())
+			return true;
+
+		SortedList<String> l = new SortedList<String>(keySet());
+		SortedList<String> lo = new SortedList<String>(other.keySet());
+		if (!l.isEqual(lo))
+			return false;
+
+		for (String key : keySet()) {
+			if (!get(key).equals(other.get(key)))
+				return false;
+		}
+		return true;
+
+	}
+
+	public Object getTyped(String adname) {
+		String s = get(adname);
+		if (s == null)
+			return null;
+
+		Type t = getType(adname);
+		return convert(t, s);
+	}
+
+	private Object convert(Type t, String s) {
+		if (t.sub == null) {
+			switch (t) {
+			case STRING:
+				return s;
+			case LONG:
+				return Long.parseLong(s.trim());
+			case VERSION:
+				return Version.parseVersion(s);
+			}
+			return null;
+		}
+		List<Object> list = new ArrayList<Object>();
+		String split[] = s.split("\\s*\\(\\?!\\),\\s*");
+		for (String p : split) {
+			p = p.replaceAll("\\\\", "");
+			list.add(convert(t.sub, p));
+		}
+		return list;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/header/OSGiHeader.java b/bundleplugin/src/main/java/aQute/libg/header/OSGiHeader.java
new file mode 100755
index 0000000..7b26f6f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/header/OSGiHeader.java
@@ -0,0 +1,138 @@
+package aQute.libg.header;
+
+import java.util.*;
+
+import aQute.libg.generics.*;
+import aQute.libg.qtokens.*;
+import aQute.libg.reporter.*;
+
+public class OSGiHeader {
+
+	static public Parameters parseHeader(String value) {
+		return parseHeader(value, null);
+	}
+
+	/**
+	 * Standard OSGi header parser. This parser can handle the format clauses
+	 * ::= clause ( ',' clause ) + clause ::= name ( ';' name ) (';' key '='
+	 * value )
+	 * 
+	 * This is mapped to a Map { name => Map { attr|directive => value } }
+	 * 
+	 * @param value
+	 *            A string
+	 * @return a Map<String,Map<String,String>>
+	 */
+	static public Parameters parseHeader(String value, Reporter logger) {
+		return parseHeader(value, logger, new Parameters());
+	}
+
+	static public Parameters parseHeader(String value, Reporter logger, Parameters result) {
+		if (value == null || value.trim().length() == 0)
+			return result;
+
+		QuotedTokenizer qt = new QuotedTokenizer(value, ";=,");
+		char del = 0;
+		do {
+			boolean hadAttribute = false;
+			Attrs clause = new Attrs();
+			List<String> aliases = Create.list();
+			String name = qt.nextToken(",;");
+
+			del = qt.getSeparator();
+			if (name == null || name.length() == 0) {
+				if (logger != null && logger.isPedantic()) {
+					logger.warning("Empty clause, usually caused by repeating a comma without any name field or by having spaces after the backslash of a property file: "
+							+ value);
+				}
+				if (name == null)
+					break;
+			} else {
+				name = name.trim();
+
+				aliases.add(name);
+				while (del == ';') {
+					String adname = qt.nextToken();
+					if ((del = qt.getSeparator()) != '=') {
+						if (hadAttribute)
+							if (logger != null) {
+								logger.error("Header contains name field after attribute or directive: "
+										+ adname
+										+ " from "
+										+ value
+										+ ". Name fields must be consecutive, separated by a ';' like a;b;c;x=3;y=4");
+							}
+						if (adname != null && adname.length() > 0)
+							aliases.add(adname.trim());
+					} else {
+						String advalue = qt.nextToken();
+						if (clause.containsKey(adname)) {
+							if (logger != null && logger.isPedantic())
+								logger.warning("Duplicate attribute/directive name " + adname
+										+ " in " + value
+										+ ". This attribute/directive will be ignored");
+						}
+						if (advalue == null) {
+							if (logger != null)
+								logger.error("No value after '=' sign for attribute " + adname);
+							advalue = "";
+						}
+						clause.put(adname.trim(), advalue.trim());
+						del = qt.getSeparator();
+						hadAttribute = true;
+					}
+				}
+
+				// Check for duplicate names. The aliases list contains
+				// the list of nams, for each check if it exists. If so,
+				// add a number of "~" to make it unique.
+				for (String clauseName : aliases) {
+					if (result.containsKey(clauseName)) {
+						if (logger != null && logger.isPedantic())
+							logger.warning("Duplicate name "
+									+ clauseName
+									+ " used in header: '"
+									+ clauseName
+									+ "'. Duplicate names are specially marked in Bnd with a ~ at the end (which is stripped at printing time).");
+						while (result.containsKey(clauseName))
+							clauseName += "~";
+					}
+					result.put(clauseName, clause);
+				}
+			}
+		} while (del == ',');
+		return result;
+	}
+
+	public static Attrs parseProperties(String input) {
+		return parseProperties(input, null);
+	}
+
+	public static Attrs parseProperties(String input, Reporter logger) {
+		if (input == null || input.trim().length() == 0)
+			return new Attrs();
+
+		Attrs result = new Attrs();
+		QuotedTokenizer qt = new QuotedTokenizer(input, "=,");
+		char del = ',';
+
+		while (del == ',') {
+			String key = qt.nextToken(",=");
+			String value = "";
+			del = qt.getSeparator();
+			if (del == '=') {
+				value = qt.nextToken(",=");
+				del = qt.getSeparator();
+			}
+			result.put(key, value);
+		}
+		if (del != 0) {
+			if (logger == null)
+				throw new IllegalArgumentException("Invalid syntax for properties: " + input);
+			logger.error("Invalid syntax for properties: " + input);
+		}
+
+		return result;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/header/Parameters.java b/bundleplugin/src/main/java/aQute/libg/header/Parameters.java
new file mode 100644
index 0000000..96f0d08
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/header/Parameters.java
@@ -0,0 +1,203 @@
+package aQute.libg.header;
+
+import java.util.*;
+
+import aQute.lib.collections.*;
+import aQute.libg.reporter.*;
+
+public class Parameters implements Map<String, Attrs> {
+	private LinkedHashMap<String, Attrs>	map;
+	static Map<String, Attrs>				EMPTY	= Collections.emptyMap();
+	String									error;
+
+	public Parameters() {
+	}
+
+	public Parameters(String header) {
+		OSGiHeader.parseHeader(header, null, this);
+	}
+
+	public Parameters(String header, Reporter reporter) {
+		OSGiHeader.parseHeader(header, reporter, this);
+	}
+
+	public void clear() {
+		map.clear();
+	}
+
+	public boolean containsKey(final String name) {
+		if (map == null)
+			return false;
+
+		return map.containsKey(name);
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public boolean containsKey(Object name) {
+		assert name instanceof String;
+		if (map == null)
+			return false;
+
+		return map.containsKey((String) name);
+	}
+
+	public boolean containsValue(Attrs value) {
+		if (map == null)
+			return false;
+
+		return map.containsValue(value);
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public boolean containsValue(Object value) {
+		assert value instanceof Attrs;
+		if (map == null)
+			return false;
+
+		return map.containsValue((Attrs) value);
+	}
+
+	public Set<java.util.Map.Entry<String, Attrs>> entrySet() {
+		if (map == null)
+			return EMPTY.entrySet();
+
+		return map.entrySet();
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public Attrs get(Object key) {
+		assert key instanceof String;
+		if (map == null)
+			return null;
+
+		return map.get((String) key);
+	}
+
+	public Attrs get(String key) {
+		if (map == null)
+			return null;
+
+		return map.get(key);
+	}
+
+	public boolean isEmpty() {
+		return map == null || map.isEmpty();
+	}
+
+	public Set<String> keySet() {
+		if (map == null)
+			return EMPTY.keySet();
+
+		return map.keySet();
+	}
+
+	public Attrs put(String key, Attrs value) {
+		assert key != null;
+		assert value != null;
+
+		if (map == null)
+			map = new LinkedHashMap<String, Attrs>();
+
+		return map.put(key, value);
+	}
+
+	public void putAll(Map<? extends String, ? extends Attrs> map) {
+		if (this.map == null) {
+			if (map.isEmpty())
+				return;
+			this.map = new LinkedHashMap<String, Attrs>();
+		}
+		this.map.putAll(map);
+	}
+	public void putAllIfAbsent(Map<String, ? extends Attrs> map) {
+		for(Map.Entry<String, ? extends Attrs> entry : map.entrySet() ) {
+			if ( !containsKey(entry.getKey()))
+				put(entry.getKey(), entry.getValue());
+		}
+	}
+
+	@SuppressWarnings("cast")
+	@Deprecated public Attrs remove(Object var0) {
+		assert var0 instanceof String;
+		if (map == null)
+			return null;
+
+		return map.remove((String) var0);
+	}
+
+	public Attrs remove(String var0) {
+		if (map == null)
+			return null;
+		return map.remove(var0);
+	}
+
+	public int size() {
+		if (map == null)
+			return 0;
+		return map.size();
+	}
+
+	public Collection<Attrs> values() {
+		if (map == null)
+			return EMPTY.values();
+
+		return map.values();
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		append(sb);
+		return sb.toString();
+	}
+
+	public void append(StringBuilder sb) {
+		String del = "";
+		for (Map.Entry<String, Attrs> s : entrySet()) {
+			sb.append(del);
+			sb.append(s.getKey());
+			if (!s.getValue().isEmpty()) {
+				sb.append(';');
+				s.getValue().append(sb);
+			}
+
+			del = ",";
+		}
+	}
+
+	@Deprecated
+	public boolean equals(Object other) {
+		return super.equals(other);
+	}
+	
+	@Deprecated
+	public int hashCode() {
+		return super.hashCode();
+	}
+	
+
+	public boolean isEqual(Parameters other) {
+		if (this == other)
+			return true;
+
+		if (size() != other.size())
+			return false;
+
+		if (isEmpty())
+			return true;
+
+		SortedList<String> l = new SortedList<String>(keySet());
+		SortedList<String> lo = new SortedList<String>(other.keySet());
+		if (!l.isEqual(lo))
+			return false;
+
+		for (String key : keySet()) {
+			if (!get(key).isEqual(other.get(key)))
+				return false;
+		}
+		return true;
+	}
+
+	public Map<String,? extends Map<String,String>> asMapMap() {
+		return this;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/header/packageinfo b/bundleplugin/src/main/java/aQute/libg/header/packageinfo
new file mode 100644
index 0000000..e39f616
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/header/packageinfo
@@ -0,0 +1 @@
+version 1.1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/log/Logger.java b/bundleplugin/src/main/java/aQute/libg/log/Logger.java
new file mode 100755
index 0000000..bb87707
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/log/Logger.java
@@ -0,0 +1,16 @@
+package aQute.libg.log;
+
+import java.util.*;
+
+
+public interface Logger {
+	void error(String s, Object ... args);
+	void warning(String s, Object ... args);
+	void progress(String s, Object ... args);
+	
+	List<String> getWarnings();
+	List<String> getErrors();
+	List<String> getProgress();
+	
+	boolean isPedantic();
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/log/packageinfo b/bundleplugin/src/main/java/aQute/libg/log/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/log/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/map/MAP.java b/bundleplugin/src/main/java/aQute/libg/map/MAP.java
new file mode 100644
index 0000000..41c588b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/map/MAP.java
@@ -0,0 +1,45 @@
+package aQute.libg.map;
+
+import java.util.*;
+
+/**
+ * Easy way to build a map:
+ * 
+ * 	Map<String,Integer> s = MAP.$("a",2).$("b",3);
+ *
+ */
+public class MAP {
+
+	static public class MAPX<K, V> extends LinkedHashMap<K, V> {
+		private static final long	serialVersionUID	= 1L;
+		public MAPX<K, V> $(K key, V value) {
+			put(key, value);
+			return this;
+		}
+
+		public MAPX<K, V> $(Map<K,V> all) {
+			putAll(all);
+			return this;
+		}
+		public Hashtable<K,V> asHashtable() {
+			return new Hashtable<K,V>(this);
+		}
+	}
+
+	public static <Kx, Vx> MAPX<Kx, Vx> $(Kx key, Vx value) {
+		MAPX<Kx, Vx> map = new MAPX<Kx, Vx>();
+		map.put(key, value);
+		return map;
+	}
+	
+	
+	public <K,V> Map<K,V> dictionary(Dictionary<K,V> dict ) {
+		Map<K,V> map = new LinkedHashMap<K, V>();
+		for ( Enumeration<K> e = dict.keys(); e.hasMoreElements(); ) {
+			K k = e.nextElement();
+			V v = dict.get(k);
+			map.put(k,v);
+		}
+		return map;
+	}
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/map/packageinfo b/bundleplugin/src/main/java/aQute/libg/map/packageinfo
new file mode 100644
index 0000000..897578f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/map/packageinfo
@@ -0,0 +1 @@
+version 1.2.0
diff --git a/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java b/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java
new file mode 100755
index 0000000..58ea539
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java
@@ -0,0 +1,116 @@
+package aQute.libg.qtokens;
+
+import java.util.*;
+
+import aQute.libg.generics.*;
+
+public class QuotedTokenizer {
+	String	string;
+	int		index				= 0;
+	String	separators;
+	boolean	returnTokens;
+	boolean	ignoreWhiteSpace	= true;
+	String	peek;
+	char	separator;
+
+	public QuotedTokenizer(String string, String separators, boolean returnTokens ) {
+		if ( string == null )
+			throw new IllegalArgumentException("string argument must be not null");
+		this.string = string;
+		this.separators = separators;
+		this.returnTokens = returnTokens;
+	}
+	public QuotedTokenizer(String string, String separators) {
+		this(string,separators,false);
+	}
+
+	public String nextToken(String separators) {
+		separator = 0;
+		if ( peek != null ) {
+			String tmp = peek;
+			peek = null;
+			return tmp;
+		}
+		
+		if ( index == string.length())
+			return null;
+		
+		StringBuilder sb = new StringBuilder();
+
+		while (index < string.length()) {
+			char c = string.charAt(index++);
+
+			if ( Character.isWhitespace(c)) {
+				if ( index == string.length())
+					break;
+				sb.append(c);
+				continue;
+			}
+			
+			if (separators.indexOf(c) >= 0) {
+				if (returnTokens)
+					peek = Character.toString(c);
+				else
+					separator = c;
+				break;
+			}
+
+			switch (c) {
+				case '"' :
+				case '\'' :
+					quotedString(sb, c);
+					break;
+
+				default :
+					sb.append(c);
+			}
+		}
+		String result = sb.toString().trim();
+		if ( result.length()==0 && index==string.length())
+			return null;
+		return result;
+	}
+
+	public String nextToken() {
+		return nextToken(separators);
+	}
+
+	private void quotedString(StringBuilder sb, char c) {
+		char quote = c;
+		while (index < string.length()) {
+			c = string.charAt(index++);
+			if (c == quote)
+				break;
+			if (c == '\\' && index < string.length()
+					&& string.charAt(index + 1) == quote)
+				c = string.charAt(index++);
+			sb.append(c);
+		}
+	}
+
+	public String[] getTokens() {
+		return getTokens(0);
+	}
+
+	private String [] getTokens(int cnt){
+		String token = nextToken();
+		if ( token == null ) 
+			return new String[cnt];
+		
+		String result[] = getTokens(cnt+1);
+		result[cnt]=token;
+		return result;
+	}
+
+	public char getSeparator() { return separator; }
+	
+	public List<String> getTokenSet() {
+		List<String> list = Create.list();
+		String token = nextToken();
+		while ( token != null ) {
+			list.add(token);
+			token = nextToken();
+		}
+		return list;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/qtokens/packageinfo b/bundleplugin/src/main/java/aQute/libg/qtokens/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/qtokens/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/reporter/Reporter.java b/bundleplugin/src/main/java/aQute/libg/reporter/Reporter.java
new file mode 100755
index 0000000..c6179af
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/reporter/Reporter.java
@@ -0,0 +1,15 @@
+package aQute.libg.reporter;
+
+import java.util.*;
+
+
+public interface Reporter {
+	void error(String s, Object ... args);
+	void warning(String s, Object ... args);
+	void progress(String s, Object ... args);
+	void trace(String s, Object ... args);
+	List<String> getWarnings();
+	List<String> getErrors();
+	
+	boolean isPedantic();
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java b/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java
new file mode 100644
index 0000000..249b776
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java
@@ -0,0 +1,182 @@
+package aQute.libg.reporter;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.libg.generics.*;
+
+/**
+ * Mainly used for testing where reporters are needed.
+ * 
+ */
+public class ReporterAdapter implements Reporter {
+	final List<String>	errors		= new ArrayList<String>();
+	final List<String>	warnings	= new ArrayList<String>();
+	final Formatter		out;
+	boolean				trace;
+	boolean				pedantic;
+	boolean				exceptions;
+
+	/**
+	 * @return the exceptions
+	 */
+	public boolean isExceptions() {
+		return exceptions;
+	}
+
+	/**
+	 * @param exceptions
+	 *            the exceptions to set
+	 */
+	public void setExceptions(boolean exceptions) {
+		this.exceptions = exceptions;
+	}
+
+	/**
+	 * @return the out
+	 */
+	public Formatter getOut() {
+		return out;
+	}
+
+	/**
+	 * @return the trace
+	 */
+	public boolean isTrace() {
+		return trace;
+	}
+
+	/**
+	 * @param pedantic
+	 *            the pedantic to set
+	 */
+	public void setPedantic(boolean pedantic) {
+		this.pedantic = pedantic;
+	}
+
+	public ReporterAdapter() {
+		out = null;
+	}
+
+	public ReporterAdapter(Appendable app) {
+		out = new Formatter(app);
+	}
+
+	public void error(String s, Object... args) {
+		String e = String.format(s, args);
+		errors.add(e);
+		trace("ERROR: %s", e);
+	}
+
+	public void exception(Throwable t, String s, Object... args) {
+		String e = String.format(s, args);
+		errors.add(e);
+		trace("ERROR: %s", e);
+		if (isExceptions() || isTrace())
+			if ( t instanceof InvocationTargetException )
+				t.getCause().printStackTrace(System.err);
+			else
+				t.printStackTrace(System.err);
+	}
+
+	public void warning(String s, Object... args) {
+		String e = String.format(s, args);
+		warnings.add(e);
+		trace("warning: %s", e);
+	}
+
+	public void progress(String s, Object... args) {
+		if (out != null) {
+			out.format(s, args);
+			if ( !s.endsWith("\n"))
+				out.format("\n");
+		}
+	}
+
+	public void trace(String s, Object... args) {
+		if (trace && out != null) {
+			out.format("# "+s+"\n", args);
+			out.flush();
+		}
+	}
+
+	public List<String> getWarnings() {
+		return warnings;
+	}
+
+	public List<String> getErrors() {
+		return errors;
+	}
+
+	public boolean isPedantic() {
+		return false;
+	}
+
+	public void setTrace(boolean b) {
+		this.trace = b;
+	}
+
+	public boolean isOk() {
+		return errors.isEmpty();
+	}
+
+	public boolean isPerfect() {
+		return isOk() && warnings.isEmpty();
+	}
+
+	public boolean check(String... pattern) {
+		Set<String> missed = Create.set();
+
+		if (pattern != null) {
+			for (String p : pattern) {
+				boolean match = false;
+				Pattern pat = Pattern.compile(p);
+				for (Iterator<String> i = errors.iterator(); i.hasNext();) {
+					if (pat.matcher(i.next()).find()) {
+						i.remove();
+						match = true;
+					}
+				}
+				for (Iterator<String> i = warnings.iterator(); i.hasNext();) {
+					if (pat.matcher(i.next()).find()) {
+						i.remove();
+						match = true;
+					}
+				}
+				if (!match)
+					missed.add(p);
+
+			}
+		}
+		if (missed.isEmpty() && isPerfect())
+			return true;
+
+		if (!missed.isEmpty())
+			error("Missed the following patterns in the warnings or errors: %s", missed);
+
+		return false;
+	}
+
+	/**
+	 * Report the errors and warnings
+	 */
+
+	public void report(Appendable out) {
+		Formatter f = new Formatter(out);
+		report("Error", getErrors(), f);
+		report("Warning", getWarnings(), f);
+		f.flush();
+	}
+
+	void report(String title, Collection<String> list, Formatter f) {
+		if (list.isEmpty())
+			return;
+		f.format(title + (list.size() > 1 ? "s" : "")+"\n");
+		int n=0;
+		for (String s : list) {
+			f.format("%3s. %s\n", n++, s);
+		}
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/reporter/packageinfo b/bundleplugin/src/main/java/aQute/libg/reporter/packageinfo
new file mode 100644
index 0000000..ef7df68
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/reporter/packageinfo
@@ -0,0 +1 @@
+version 1.2
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/ContentFilter.java b/bundleplugin/src/main/java/aQute/libg/sax/ContentFilter.java
new file mode 100644
index 0000000..62ca259
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/ContentFilter.java
@@ -0,0 +1,8 @@
+package aQute.libg.sax;
+
+import org.xml.sax.ContentHandler;
+
+public interface ContentFilter extends ContentHandler {
+	void setParent(ContentHandler parent);
+	ContentHandler getParent();
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/ContentFilterImpl.java b/bundleplugin/src/main/java/aQute/libg/sax/ContentFilterImpl.java
new file mode 100644
index 0000000..7f71568
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/ContentFilterImpl.java
@@ -0,0 +1,71 @@
+package aQute.libg.sax;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+public class ContentFilterImpl implements ContentFilter {
+
+	private ContentHandler parent;
+
+	public void setParent(ContentHandler parent) {
+		this.parent = parent;
+		
+	}
+
+	public ContentHandler getParent() {
+		return parent;
+	}
+
+	public void setDocumentLocator(Locator locator) {
+		parent.setDocumentLocator(locator);
+	}
+
+	public void startDocument() throws SAXException {
+		parent.startDocument();
+	}
+
+	public void endDocument() throws SAXException {
+		parent.endDocument();
+	}
+
+	public void startPrefixMapping(String prefix, String uri)
+			throws SAXException {
+		parent.startPrefixMapping(prefix, uri);
+	}
+
+	public void endPrefixMapping(String prefix) throws SAXException {
+		parent.endPrefixMapping(prefix);
+	}
+
+	public void startElement(String uri, String localName, String qName,
+			Attributes atts) throws SAXException {
+		parent.startElement(uri, localName, qName, atts);
+	}
+
+	public void endElement(String uri, String localName, String qName)
+			throws SAXException {
+		parent.endElement(uri, localName, qName);
+	}
+
+	public void characters(char[] ch, int start, int length)
+			throws SAXException {
+		parent.characters(ch, start, length);
+	}
+
+	public void ignorableWhitespace(char[] ch, int start, int length)
+			throws SAXException {
+		parent.ignorableWhitespace(ch, start, length);
+	}
+
+	public void processingInstruction(String target, String data)
+			throws SAXException {
+		parent.processingInstruction(target, data);
+	}
+
+	public void skippedEntity(String name) throws SAXException {
+		parent.skippedEntity(name);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/SAXElement.java b/bundleplugin/src/main/java/aQute/libg/sax/SAXElement.java
new file mode 100644
index 0000000..b7ce35e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/SAXElement.java
@@ -0,0 +1,36 @@
+package aQute.libg.sax;
+
+import org.xml.sax.Attributes;
+
+public class SAXElement {
+
+	private final String uri;
+	private final String localName;
+	private final String qName;
+	private final Attributes atts;
+
+	public SAXElement(String uri, String localName, String qName,
+			Attributes atts) {
+		this.uri = uri;
+		this.localName = localName;
+		this.qName = qName;
+		this.atts = atts;
+	}
+
+	public String getUri() {
+		return uri;
+	}
+
+	public String getLocalName() {
+		return localName;
+	}
+
+	public String getqName() {
+		return qName;
+	}
+
+	public Attributes getAtts() {
+		return atts;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/SAXUtil.java b/bundleplugin/src/main/java/aQute/libg/sax/SAXUtil.java
new file mode 100644
index 0000000..87b058e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/SAXUtil.java
@@ -0,0 +1,29 @@
+package aQute.libg.sax;
+
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.XMLReader;
+
+public class SAXUtil {
+	
+	public static XMLReader buildPipeline(Result output, ContentFilter... filters) throws Exception {
+		SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+		TransformerHandler handler = factory.newTransformerHandler();
+		handler.setResult(output);
+		
+		ContentHandler last = handler;
+		if (filters != null) for (ContentFilter filter : filters) {
+			filter.setParent(last);
+			last = filter;
+		}
+		XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+		reader.setContentHandler(last);
+		
+		return reader;
+	}
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/filters/ElementSelectionFilter.java b/bundleplugin/src/main/java/aQute/libg/sax/filters/ElementSelectionFilter.java
new file mode 100644
index 0000000..4411db9
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/filters/ElementSelectionFilter.java
@@ -0,0 +1,49 @@
+package aQute.libg.sax.filters;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import aQute.libg.sax.ContentFilterImpl;
+
+public abstract class ElementSelectionFilter extends ContentFilterImpl{
+	
+	int depth = 0;
+	int hiddenDepth = -1;
+	
+	protected abstract boolean select(int depth, String uri, String localName, String qName, Attributes attribs);
+	
+	@Override
+	public final void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+		if (hiddenDepth < 0) {
+			boolean allow = select(depth, uri, localName, qName, atts);
+			if (allow)
+				super.startElement(uri, localName, qName, atts);
+			else
+				hiddenDepth = 0;
+		} else {
+			hiddenDepth ++;
+		}
+		depth++;
+	}
+	
+	@Override
+	public final void endElement(String uri, String localName, String qName) throws SAXException {
+		if (hiddenDepth < 0) {
+			super.endElement(uri, localName, qName);
+		} else {
+			hiddenDepth --;
+		}
+		depth --;
+	}
+	
+	@Override
+	public void characters(char[] ch, int start, int length) throws SAXException {
+		if (hiddenDepth < 0) super.characters(ch, start, length);
+	}
+	
+	@Override
+	public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+		if (hiddenDepth < 0) super.ignorableWhitespace(ch, start, length);
+	}
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/filters/MergeContentFilter.java b/bundleplugin/src/main/java/aQute/libg/sax/filters/MergeContentFilter.java
new file mode 100644
index 0000000..acf5d12
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/filters/MergeContentFilter.java
@@ -0,0 +1,57 @@
+package aQute.libg.sax.filters;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import aQute.libg.sax.ContentFilterImpl;
+import aQute.libg.sax.SAXElement;
+
+public class MergeContentFilter extends ContentFilterImpl {
+
+	private int elementDepth = 0;
+	
+	private final List<SAXElement> rootElements = new LinkedList<SAXElement>();
+	
+	@Override
+	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+		if (elementDepth++ == 0) {
+			if (rootElements.isEmpty())
+				super.startElement(uri, localName, qName, atts);
+			else if (!rootElements.get(0).getqName().equals(qName))
+				throw new SAXException(String.format("Documents have inconsistent root element names: first was %s, current is %s.", rootElements.get(0).getqName(), qName));
+			rootElements.add(new SAXElement(uri, localName, qName, atts));
+		} else {
+			super.startElement(uri, localName, qName, atts);
+		}
+	}
+
+	@Override
+	public void endElement(String uri, String localName, String qName) throws SAXException {
+		if (--elementDepth > 0) {
+			super.endElement(uri, localName, qName);
+		}
+	}
+	
+	@Override
+	public void processingInstruction(String target, String data) throws SAXException {
+		if (rootElements.isEmpty())
+			super.processingInstruction(target, data);
+	}
+	
+	public void closeRootAndDocument() throws SAXException {
+		if (!rootElements.isEmpty()) {
+			SAXElement root = rootElements.get(0);
+			super.endElement(root.getUri(), root.getLocalName(), root.getqName());
+		}
+		super.endDocument();
+	}
+	
+	public List<SAXElement> getRootElements() {
+		return Collections.unmodifiableList(rootElements);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/filters/packageinfo b/bundleplugin/src/main/java/aQute/libg/sax/filters/packageinfo
new file mode 100644
index 0000000..a4f1546
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/filters/packageinfo
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/sax/packageinfo b/bundleplugin/src/main/java/aQute/libg/sax/packageinfo
new file mode 100644
index 0000000..a4f1546
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sax/packageinfo
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/sed/Replacer.java b/bundleplugin/src/main/java/aQute/libg/sed/Replacer.java
new file mode 100644
index 0000000..fa181f4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sed/Replacer.java
@@ -0,0 +1,5 @@
+package aQute.libg.sed;
+
+public interface Replacer {
+    String process(String line);
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sed/Sed.java b/bundleplugin/src/main/java/aQute/libg/sed/Sed.java
new file mode 100644
index 0000000..74f4756
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sed/Sed.java
@@ -0,0 +1,99 @@
+package aQute.libg.sed;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+public class Sed {
+    final File                 file;
+    final Replacer             macro;
+    File                       output;
+    boolean backup = true;
+
+    final Map<Pattern, String> replacements = new LinkedHashMap<Pattern, String>();
+
+    public Sed(Replacer macro, File file) {
+        assert file.isFile();
+        this.file = file;
+        this.macro = macro;
+    }
+    
+    public Sed(File file) {
+        assert file.isFile();
+        this.file = file;
+        this.macro = null;
+    }
+
+    public void setOutput(File f) {
+        output = f;
+    }
+
+    public void replace(String pattern, String replacement) {
+        replacements.put(Pattern.compile(pattern), replacement);
+    }
+
+    public int doIt() throws IOException {
+    	int actions = 0;
+        BufferedReader brdr = new BufferedReader(new InputStreamReader( new FileInputStream(file),"UTF-8"));
+        File out;
+        if (output != null)
+            out = output;
+        else
+            out = new File(file.getAbsolutePath() + ".tmp");
+        PrintWriter pw = new PrintWriter(new OutputStreamWriter( new FileOutputStream(out),"UTF-8"));
+        try {
+            String line;
+            while ((line = brdr.readLine()) != null) {
+                for (Pattern p : replacements.keySet()) {
+                    String replace = replacements.get(p);
+                    Matcher m = p.matcher(line);
+
+                    StringBuffer sb = new StringBuffer();
+                    while (m.find()) {
+                        String tmp = setReferences(m, replace);
+                        if ( macro != null)
+                        	tmp = Matcher.quoteReplacement(macro.process(tmp));
+                        m.appendReplacement(sb, tmp);
+                        actions++;
+                    }
+                    m.appendTail(sb);
+
+                    line = sb.toString();
+                }
+                pw.println(line);
+            }
+            pw.close();
+            if (output == null) {
+            	if ( backup ) {
+                    File bak = new File(file.getAbsolutePath() + ".bak");
+            		file.renameTo(bak);
+            	}
+                out.renameTo(file);
+            }
+        } finally {
+            brdr.close();
+            pw.close();
+        }
+        return actions;
+    }
+
+    private String setReferences(Matcher m, String replace) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < replace.length(); i++) {
+            char c = replace.charAt(i);
+            if (c == '$' && i < replace.length() - 1
+                    && Character.isDigit(replace.charAt(i + 1))) {
+                int n = replace.charAt(i + 1) - '0';
+                if ( n <= m.groupCount() )
+                    sb.append(m.group(n));
+                i++;
+            } else
+                sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    public void setBackup(boolean b) {
+    	this.backup=b;
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/sed/packageinfo b/bundleplugin/src/main/java/aQute/libg/sed/packageinfo
new file mode 100644
index 0000000..b3d1f97
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/sed/packageinfo
@@ -0,0 +1 @@
+version 1.0.1
diff --git a/bundleplugin/src/main/java/aQute/libg/tarjan/Tarjan.java b/bundleplugin/src/main/java/aQute/libg/tarjan/Tarjan.java
new file mode 100644
index 0000000..3d05b76
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/tarjan/Tarjan.java
@@ -0,0 +1,108 @@
+package aQute.libg.tarjan;
+
+import static java.lang.Math.*;
+
+import java.util.*;
+
+public class Tarjan<T> {
+
+	public class Node {
+		final T				name;
+		final List<Node>	adjacent	= new ArrayList<Node>();
+		int					low			= -1;
+		int					index		= -1;
+
+		public Node(T name) {
+			this.name = name;
+		}
+		
+		public String toString() {
+			return name + "{" + index + "," + low + "}";
+		}
+	}
+
+	private int				index	= 0;
+	private List<Node>		stack	= new ArrayList<Node>();
+	private Set<Set<T>>	scc		= new HashSet<Set<T>>();
+	private Node			root	= new Node(null);
+
+	
+//	   public ArrayList<ArrayList<Node>> tarjan(Node v, AdjacencyList list){
+//	       v.index = index;
+//	       v.lowlink = index;
+//	       index++;
+//	       stack.add(0, v);
+//	       for(Edge e : list.getAdjacent(v)){
+//	           Node n = e.to;
+//	           if(n.index == -1){
+//	               tarjan(n, list);
+//	               v.lowlink = Math.min(v.lowlink, n.lowlink);
+//	           }else if(stack.contains(n)){
+//	               v.lowlink = Math.min(v.lowlink, n.index);
+//	           }
+//	       }
+//	       if(v.lowlink == v.index){
+//	           Node n;
+//	           ArrayList<Node> component = new ArrayList<Node>();
+//	           do{
+//	               n = stack.remove(0);
+//	               component.add(n);
+//	           }while(n != v);
+//	           SCC.add(component);
+//	       }
+//	       return SCC;
+//	   }
+
+	void tarjan(Node v) {
+		v.index = index;
+		v.low = index;
+		index++;
+		stack.add(0, v);
+		for (Node n : v.adjacent) {
+			if (n.index == -1) {
+				// first time visit
+				tarjan(n);
+				v.low = min(v.low, n.low);
+			} else if (stack.contains(n)) {
+				v.low = min(v.low, n.index);
+			}
+		}
+
+		if (v!=root && v.low == v.index) {
+			Set<T> component = new HashSet<T>();
+			Node n;
+			do {
+				n = stack.remove(0);
+				component.add(n.name);
+			} while (n != v);
+			scc.add(component);
+		}
+	}
+
+	Set<Set<T>> getResult(Map<T, ? extends Collection<T>> graph) {
+		Map<T, Node> index = new HashMap<T, Node>();
+
+		for (Map.Entry<T, ? extends Collection<T>> entry : graph.entrySet()) {
+			Node node = getNode(index, entry.getKey());
+			root.adjacent.add(node);
+			for (T adj : entry.getValue())
+				node.adjacent.add(getNode(index, adj));
+		}
+		tarjan(root);
+		return scc;
+	}
+
+	private Node getNode(Map<T, Node> index, T key) {
+		Node node = index.get(key);
+		if (node == null) {
+			node = new Node(key);
+			index.put(key, node);
+		}
+		return node;
+	}
+
+	public static <T> Collection<? extends Collection<T>> tarjan(Map<T, ? extends Collection<T>> graph) {
+		Tarjan<T> tarjan = new Tarjan<T>();
+		return tarjan.getResult(graph);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/tarjan/packageinfo b/bundleplugin/src/main/java/aQute/libg/tarjan/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/tarjan/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/tuple/Pair.java b/bundleplugin/src/main/java/aQute/libg/tuple/Pair.java
new file mode 100644
index 0000000..efb2298
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/tuple/Pair.java
@@ -0,0 +1,11 @@
+package aQute.libg.tuple;
+
+public class Pair<A,B> {
+	final public A a;
+	final public B b;
+	
+	public Pair(A a, B b) {
+		this.a = a;
+		this.b = b;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/tuple/packageinfo b/bundleplugin/src/main/java/aQute/libg/tuple/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/tuple/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/libg/version/Version.java b/bundleplugin/src/main/java/aQute/libg/version/Version.java
new file mode 100755
index 0000000..d326ec4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/version/Version.java
@@ -0,0 +1,166 @@
+package aQute.libg.version;
+
+import java.util.regex.*;
+
+public class Version implements Comparable<Version> {
+    final int                   major;
+    final int                   minor;
+    final int                   micro;
+    final String                qualifier;
+    public final static String  VERSION_STRING = "(\\d+)(\\.(\\d+)(\\.(\\d+)(\\.([-_\\da-zA-Z]+))?)?)?";
+    public final static Pattern VERSION        = Pattern
+                                                       .compile(VERSION_STRING);
+    public final static Version LOWEST         = new Version();
+    public final static Version HIGHEST        = new Version(Integer.MAX_VALUE,
+                                                       Integer.MAX_VALUE,
+                                                       Integer.MAX_VALUE,
+                                                       "\uFFFF");
+
+    public static final Version	emptyVersion	= LOWEST;
+    public static final Version	ONE	= new Version(1,0,0);
+
+    public Version() {
+        this(0);
+    }
+
+    public Version(int major, int minor, int micro, String qualifier) {
+        this.major = major;
+        this.minor = minor;
+        this.micro = micro;
+        this.qualifier = qualifier;
+    }
+
+    public Version(int major, int minor, int micro) {
+        this(major, minor, micro, null);
+    }
+
+    public Version(int major, int minor) {
+        this(major, minor, 0, null);
+    }
+
+    public Version(int major) {
+        this(major, 0, 0, null);
+    }
+
+    public Version(String version) {
+    	version = version.trim();
+        Matcher m = VERSION.matcher(version);
+        if (!m.matches())
+            throw new IllegalArgumentException("Invalid syntax for version: "
+                    + version);
+
+        major = Integer.parseInt(m.group(1));
+        if (m.group(3) != null)
+            minor = Integer.parseInt(m.group(3));
+        else
+            minor = 0;
+
+        if (m.group(5) != null)
+            micro = Integer.parseInt(m.group(5));
+        else
+            micro = 0;
+
+        qualifier = m.group(7);
+    }
+
+    public int getMajor() {
+        return major;
+    }
+
+    public int getMinor() {
+        return minor;
+    }
+
+    public int getMicro() {
+        return micro;
+    }
+
+    public String getQualifier() {
+        return qualifier;
+    }
+
+    public int compareTo(Version other) {
+        if (other == this)
+            return 0;
+
+        Version o = other;
+        if (major != o.major)
+            return major - o.major;
+
+        if (minor != o.minor)
+            return minor - o.minor;
+
+        if (micro != o.micro)
+            return micro - o.micro;
+
+        int c = 0;
+        if (qualifier != null)
+            c = 1;
+        if (o.qualifier != null)
+            c += 2;
+
+        switch (c) {
+        case 0:
+            return 0;
+        case 1:
+            return 1;
+        case 2:
+            return -1;
+        }
+        return qualifier.compareTo(o.qualifier);
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(major);
+        sb.append(".");
+        sb.append(minor);
+        sb.append(".");
+        sb.append(micro);
+        if (qualifier != null) {
+            sb.append(".");
+            sb.append(qualifier);
+        }
+        return sb.toString();
+    }
+
+    public boolean equals(Object ot) {
+        if ( ! (ot instanceof Version))
+            return false;
+        
+        return compareTo((Version)ot) == 0;
+    }
+
+    public int hashCode() {
+        return major * 97 ^ minor * 13 ^ micro
+                + (qualifier == null ? 97 : qualifier.hashCode());
+    }
+
+    public int get(int i) {
+        switch(i) {
+        case 0 : return major;
+        case 1 : return minor;
+        case 2 : return micro;
+        default:
+            throw new IllegalArgumentException("Version can only get 0 (major), 1 (minor), or 2 (micro)");
+        }
+    }
+    
+    public static Version parseVersion(String version) {
+		if (version == null) {
+			return LOWEST;
+		}
+
+		version = version.trim();
+		if (version.length() == 0) {
+			return LOWEST;
+		}
+
+		return new Version(version);
+
+    }
+    
+    public Version getWithoutQualifier() {
+    	return new Version(major,minor,micro);
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/version/VersionRange.java b/bundleplugin/src/main/java/aQute/libg/version/VersionRange.java
new file mode 100755
index 0000000..46f9e94
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/version/VersionRange.java
@@ -0,0 +1,96 @@
+package aQute.libg.version;
+
+import java.util.*;
+import java.util.regex.*;
+
+public class VersionRange {
+	Version			high;
+	Version			low;
+	char			start	= '[';
+	char			end		= ']';
+
+	static Pattern	RANGE	= Pattern.compile("(\\(|\\[)\\s*(" +
+									Version.VERSION_STRING + ")\\s*,\\s*(" +
+									Version.VERSION_STRING + ")\\s*(\\)|\\])");
+
+	public VersionRange(String string) {
+		string = string.trim();
+		Matcher m = RANGE.matcher(string);
+		if (m.matches()) {
+			start = m.group(1).charAt(0);
+			String v1 = m.group(2);
+			String v2 = m.group(10);
+			low = new Version(v1);
+			high = new Version(v2);
+			end = m.group(18).charAt(0);
+			if (low.compareTo(high) > 0)
+				throw new IllegalArgumentException(
+						"Low Range is higher than High Range: " + low + "-" +
+								high);
+
+		} else
+			high = low = new Version(string);
+	}
+
+	public boolean isRange() {
+		return high != low;
+	}
+
+	public boolean includeLow() {
+		return start == '[';
+	}
+
+	public boolean includeHigh() {
+		return end == ']';
+	}
+
+	public String toString() {
+		if (high == low)
+			return high.toString();
+
+		StringBuilder sb = new StringBuilder();
+		sb.append(start);
+		sb.append(low);
+		sb.append(',');
+		sb.append(high);
+		sb.append(end);
+		return sb.toString();
+	}
+
+	public Version getLow() {
+		return low;
+	}
+
+	public Version getHigh() {
+		return high;
+	}
+
+	public boolean includes(Version v) {
+		if ( !isRange() ) {
+			return low.compareTo(v) <=0;
+		}
+		if (includeLow()) {
+			if (v.compareTo(low) < 0)
+				return false;
+		} else if (v.compareTo(low) <= 0)
+			return false;
+
+		if (includeHigh()) {
+			if (v.compareTo(high) > 0)
+				return false;
+		} else if (v.compareTo(high) >= 0)
+			return false;
+		
+		return true;
+	}
+	
+	public Iterable<Version> filter( final Iterable<Version> versions) {
+		List<Version> list = new ArrayList<Version>();
+		for ( Version v : versions) {
+			if ( includes(v))
+				list.add(v);
+		}
+		return list;
+	}
+	
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/version/packageinfo b/bundleplugin/src/main/java/aQute/libg/version/packageinfo
new file mode 100644
index 0000000..b3d1f97
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/version/packageinfo
@@ -0,0 +1 @@
+version 1.0.1
diff --git a/bundleplugin/src/main/java/aQute/libg/xslt/Transform.java b/bundleplugin/src/main/java/aQute/libg/xslt/Transform.java
new file mode 100644
index 0000000..ea0fcd0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/xslt/Transform.java
@@ -0,0 +1,44 @@
+package aQute.libg.xslt;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+
+public class Transform {
+    static TransformerFactory transformerFactory = TransformerFactory
+                                                         .newInstance();
+
+    static Map<URL, Templates> cache          = new ConcurrentHashMap<URL, Templates>();
+
+    public static void transform(TransformerFactory transformerFactory, URL xslt, InputStream in, OutputStream out)
+            throws Exception {
+        if ( xslt == null ) 
+            throw new IllegalArgumentException("No source template specified");
+        
+        Templates templates = cache.get(xslt);
+        if (templates == null) {
+            InputStream xsltIn = xslt.openStream();
+            try {
+                templates = transformerFactory
+                        .newTemplates(new StreamSource(xsltIn));
+
+                cache.put(xslt, templates);
+            } finally {
+                in.close();
+            }
+        }
+        Result xmlResult = new StreamResult(out);
+        Source xmlSource = new StreamSource(in);
+        Transformer t = templates.newTransformer();
+        t.transform(xmlSource, xmlResult);
+        out.flush();
+    }
+
+    public static void transform(URL xslt, InputStream in, OutputStream out) throws Exception {
+        transform(transformerFactory,xslt, in, out);
+    }
+}
diff --git a/bundleplugin/src/main/java/aQute/libg/xslt/packageinfo b/bundleplugin/src/main/java/aQute/libg/xslt/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/libg/xslt/packageinfo
@@ -0,0 +1 @@
+version 1.0