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/lib/base64/Base64.java b/bundleplugin/src/main/java/aQute/lib/base64/Base64.java
new file mode 100755
index 0000000..151adc4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/base64/Base64.java
@@ -0,0 +1,158 @@
+package aQute.lib.base64;
+
+import java.io.*;
+
+/*
+ * Base 64 converter.
+ *
+ * TODO Implement string to byte[]
+ */
+public class Base64 {
+ byte[] data;
+
+ static final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ static byte[] values = new byte[128];
+
+ static {
+ for (int i = 0; i < values.length; i++) {
+ values[i] = -1;
+ }
+ // Create reverse index
+ for (int i = 0; i < alphabet.length(); i++) {
+ char c = alphabet.charAt(i);
+ values[c] = (byte) i;
+ }
+ }
+
+ public Base64(byte data[]) {
+ this.data = data;
+ }
+
+
+
+ public final static byte[] decodeBase64(String string) {
+ ByteArrayOutputStream bout= new ByteArrayOutputStream(string.length()*2/3);
+ StringReader rdr = new StringReader(string.trim());
+ try {
+ decode(rdr,bout);
+ } catch (Exception e) {
+ // cannot happen
+ }
+ return bout.toByteArray();
+ }
+
+ public final static void decode(Reader rdr, OutputStream out) throws Exception {
+ int register = 0;
+ int i = 0;
+ int pads = 0;
+
+ byte test[] = new byte[3];
+ int c;
+ while ((c=rdr.read()) >= 0) {
+
+ if (c > 0x7F)
+ throw new IllegalArgumentException(
+ "Invalid base64 character in " + rdr
+ + ", character value > 128 ");
+
+ int v = 0;
+ if ( c == '=' ) {
+ pads++;
+ } else {
+ v = values[c];
+ if ( v < 0 )
+ throw new IllegalArgumentException(
+ "Invalid base64 character in " + rdr + ", " + c );
+ }
+ register <<= 6;
+ register |= v;
+ test[2] = (byte) (register & 0xFF);
+ test[1] = (byte) ((register >> 8) & 0xFF);
+ test[0] = (byte) ((register >> 16) & 0xFF);
+
+ i++;
+
+ if ((i % 4) == 0) {
+ flush(out, register, pads);
+ register = 0;
+ pads = 0;
+ }
+ }
+ }
+
+ static private void flush(OutputStream out, int register, int pads) throws IOException {
+ switch (pads) {
+ case 0:
+ out.write(0xFF & (register >> 16));
+ out.write(0xFF & (register >> 8));
+ out.write(0xFF & (register >> 0));
+ break;
+
+ case 1:
+ out.write(0xFF & (register >> 16));
+ out.write(0xFF & (register >> 8));
+ break;
+
+ case 2:
+ out.write(0xFF & (register >> 16));
+ }
+ }
+
+ public Base64(String s) {
+ data = decodeBase64(s);
+ }
+
+ public String toString() {
+ return encodeBase64(data);
+ }
+
+ public static String encodeBase64(byte data[]) {
+ StringWriter sw = new StringWriter();
+ ByteArrayInputStream bin = new ByteArrayInputStream(data);
+ try {
+ encode(bin,sw);
+ } catch (IOException e) {
+ // can't happen
+ }
+ return sw.toString();
+ }
+
+
+ public Object toData() {
+ return data;
+ }
+
+ public static void encode(InputStream in, Appendable sb) throws IOException {
+ //StringBuilder sb = new StringBuilder();
+ int buf = 0;
+ int bits = 0;
+ int out = 0;
+
+ while (true) {
+ if (bits >= 6) {
+ bits -= 6;
+ int v = 0x3F & (buf >> bits);
+ sb.append(alphabet.charAt(v));
+ out++;
+ } else {
+ int c = in.read();
+ if (c < 0)
+ break;
+
+ buf <<= 8;
+ buf |= 0xFF & c;
+ bits += 8;
+ }
+ }
+ if (bits != 0) {// must be less than 7
+ sb.append(alphabet.charAt(0x3F & (buf << (6 - bits))));
+ out++;
+ }
+ int mod = 4 - (out % 4);
+ if (mod != 4) {
+ for (int i = 0; i < mod; i++)
+ sb.append('=');
+ }
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/base64/packageinfo b/bundleplugin/src/main/java/aQute/lib/base64/packageinfo
new file mode 100644
index 0000000..e39f616
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/base64/packageinfo
@@ -0,0 +1 @@
+version 1.1.0
diff --git a/bundleplugin/src/main/java/aQute/lib/codec/Codec.java b/bundleplugin/src/main/java/aQute/lib/codec/Codec.java
new file mode 100644
index 0000000..6de09cf
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/codec/Codec.java
@@ -0,0 +1,9 @@
+package aQute.lib.codec;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+public interface Codec {
+ Object decode(Reader in, Type type) throws Exception;
+ void encode(Type t, Object o, Appendable out) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/codec/HCodec.java b/bundleplugin/src/main/java/aQute/lib/codec/HCodec.java
new file mode 100644
index 0000000..f97e695
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/codec/HCodec.java
@@ -0,0 +1,72 @@
+package aQute.lib.codec;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+public class HCodec implements Codec {
+ final Codec codec;
+
+ public HCodec(Codec codec) {
+ this.codec = codec;
+ }
+
+ public Object decode(Reader in, Type type) throws Exception {
+ return codec.decode(in, type);
+ }
+
+ public <T> T decode(InputStream in, Class<T> t) throws Exception {
+ return t.cast(decode(in, (Type)t));
+ }
+
+ public <T> T decode(Reader in, Class<T> t) throws Exception {
+ return t.cast(decode(in, (Type) t));
+ }
+
+ public Object decode(InputStream in, Type t) throws Exception {
+ InputStreamReader r = new InputStreamReader(in, "UTF-8");
+ return codec.decode(r, t);
+ }
+
+ public void encode(Type t, Object o, Appendable out) throws Exception {
+ codec.encode(t, o, out);
+ }
+
+ public void encode(Type t, Object o, OutputStream out) throws Exception {
+ OutputStreamWriter wr = new OutputStreamWriter(out, "UTF-8");
+ try {
+ codec.encode(t, o, wr);
+ } finally {
+ wr.flush();
+ }
+ }
+
+ public <T> T decode(File in, Class<T> t) throws Exception {
+ FileInputStream fin = new FileInputStream(in);
+ try {
+ InputStreamReader rdr = new InputStreamReader(fin, "UTF-8");
+ try {
+ return t.cast(decode(rdr, t));
+ } finally {
+ rdr.close();
+ }
+ } finally {
+ fin.close();
+ }
+
+ }
+
+ public void encode(Type t, Object o, File out) throws Exception {
+ OutputStream oout = new FileOutputStream(out);
+ try {
+ Writer wr = new OutputStreamWriter(oout, "UTF-8");
+ try {
+ codec.encode(t, o, wr);
+ } finally {
+ wr.close();
+ }
+ } finally {
+ oout.close();
+ }
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/codec/packageinfo b/bundleplugin/src/main/java/aQute/lib/codec/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/codec/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/EnumerationIterator.java b/bundleplugin/src/main/java/aQute/lib/collections/EnumerationIterator.java
new file mode 100644
index 0000000..ec7aec6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/EnumerationIterator.java
@@ -0,0 +1,42 @@
+package aQute.lib.collections;
+
+import java.util.*;
+
+/**
+ * Simple facade for enumerators so they can be used in for loops.
+ *
+ * @param <T>
+ */
+public class EnumerationIterator<T> implements Iterable<T>, Iterator<T> {
+
+ public static <T> EnumerationIterator<T> iterator(Enumeration<T> e) {
+ return new EnumerationIterator<T>(e);
+ }
+
+ final Enumeration<T> enumerator;
+ volatile boolean done = false;
+
+ public EnumerationIterator(Enumeration<T> e) {
+ enumerator = e;
+ }
+
+ public synchronized Iterator<T> iterator() {
+ if (done)
+ throw new IllegalStateException("Can only be used once");
+ done = true;
+ return this;
+
+ }
+
+ public boolean hasNext() {
+ return enumerator.hasMoreElements();
+ }
+
+ public T next() {
+ return enumerator.nextElement();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("Does not support removes");
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/ExtList.java b/bundleplugin/src/main/java/aQute/lib/collections/ExtList.java
new file mode 100644
index 0000000..4c4f558
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/ExtList.java
@@ -0,0 +1,31 @@
+package aQute.lib.collections;
+
+import java.util.*;
+
+public class ExtList<T> extends ArrayList<T> {
+ private static final long serialVersionUID = 1L;
+
+ public ExtList(T ... ts) {
+ super(ts.length);
+ for (T t : ts){
+ add(t);
+ }
+ }
+
+ public String join() {
+ return join(",");
+ }
+
+ public String join(String del) {
+ StringBuilder sb = new StringBuilder();
+ String d= "";
+ for ( T t : this) {
+ sb.append(d);
+ d=del;
+ if ( t != null)
+ sb.append(t.toString());
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/IteratorList.java b/bundleplugin/src/main/java/aQute/lib/collections/IteratorList.java
new file mode 100644
index 0000000..cb96d16
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/IteratorList.java
@@ -0,0 +1,12 @@
+package aQute.lib.collections;
+
+import java.util.*;
+
+public class IteratorList<T> extends ArrayList<T> {
+ private static final long serialVersionUID = 1L;
+
+ public IteratorList(Iterator<T> i){
+ while(i.hasNext())
+ add(i.next());
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/LineCollection.java b/bundleplugin/src/main/java/aQute/lib/collections/LineCollection.java
new file mode 100644
index 0000000..a04ab36
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/LineCollection.java
@@ -0,0 +1,54 @@
+package aQute.lib.collections;
+
+import java.io.*;
+import java.util.*;
+
+public class LineCollection implements Iterator<String>, Closeable {
+ final BufferedReader reader;
+ String next;
+
+ public LineCollection(InputStream in) throws IOException {
+ this(new InputStreamReader(in, "UTF8"));
+ }
+
+ public LineCollection(File in) throws IOException {
+ this(new InputStreamReader( new FileInputStream(in),"UTF-8"));
+ }
+
+ public LineCollection(Reader reader) throws IOException {
+ this(new BufferedReader(reader));
+ }
+
+ public LineCollection(BufferedReader reader) throws IOException {
+ this.reader = reader;
+ next = reader.readLine();
+ }
+
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ public String next() {
+ if (next == null)
+ throw new IllegalStateException("Iterator has finished");
+ try {
+ String result = next;
+ next = reader.readLine();
+ if (next == null)
+ reader.close();
+ return result;
+ } catch (Exception e) {
+ // ignore
+ return null;
+ }
+ }
+
+ public void remove() {
+ if (next == null)
+ throw new UnsupportedOperationException("Cannot remove");
+ }
+
+ public void close() throws IOException {
+ reader.close();
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/Logic.java b/bundleplugin/src/main/java/aQute/lib/collections/Logic.java
new file mode 100644
index 0000000..75322dd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/Logic.java
@@ -0,0 +1,22 @@
+package aQute.lib.collections;
+
+import java.util.*;
+
+public class Logic {
+
+ public static <T> Collection<T> retain( Collection<T> first, Collection<T> ... sets) {
+ Set<T> result = new HashSet<T>(first);
+ for ( Collection<T> set : sets ) {
+ result.retainAll(set);
+ }
+ return result;
+ }
+
+ public static <T> Collection<T> remove( Collection<T> first, Collection<T> ... sets) {
+ Set<T> result = new HashSet<T>(first);
+ for ( Collection<T> set : sets ) {
+ result.removeAll(set);
+ }
+ return result;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/MultiMap.java b/bundleplugin/src/main/java/aQute/lib/collections/MultiMap.java
new file mode 100644
index 0000000..fcf28ac
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/MultiMap.java
@@ -0,0 +1,155 @@
+package aQute.lib.collections;
+
+import java.util.*;
+
+public class MultiMap<K,V> extends HashMap<K,List<V>> {
+ private static final long serialVersionUID = 1L;
+ final boolean noduplicates;
+ final Class<?> keyClass;
+ final Class<?> valueClass;
+
+ final Set<V> EMPTY = Collections.emptySet();
+
+ public MultiMap() {
+ noduplicates = false;
+ keyClass = Object.class;
+ valueClass = Object.class;
+ }
+
+ public MultiMap(Class<K> keyClass, Class<V> valueClass, boolean noduplicates ) {
+ this.noduplicates = noduplicates;
+ this.keyClass = keyClass;
+ this.valueClass = valueClass;
+ }
+
+ @SuppressWarnings("unchecked") public boolean add( K key, V value ) {
+ assert keyClass.isInstance(key);
+ assert valueClass.isInstance(value);
+
+ List<V> set = get(key);
+ if ( set == null) {
+ set=new ArrayList<V>();
+ if ( valueClass != Object.class) {
+ set = Collections.checkedList(set, (Class<V>)valueClass);
+ }
+ put(key,set);
+ }else {
+ if (noduplicates) {
+ if ( set.contains(value))
+ return false;
+ }
+ }
+ return set.add(value);
+ }
+
+ @SuppressWarnings("unchecked") public boolean addAll( K key, Collection<? extends V> value ) {
+ assert keyClass.isInstance(key);
+ List<V> set = get(key);
+ if ( set == null) {
+ set=new ArrayList<V>();
+ if ( valueClass != Object.class) {
+ set = Collections.checkedList(set, (Class<V>)valueClass);
+ }
+ put(key,set);
+ } else
+ if ( noduplicates) {
+ boolean r=false;
+ for ( V v : value) {
+ assert valueClass.isInstance(v);
+ if ( !set.contains(value))
+ r|=set.add(v);
+ }
+ return r;
+ }
+ return set.addAll(value);
+ }
+
+ public boolean remove( K key, V value ) {
+ assert keyClass.isInstance(key);
+ assert valueClass.isInstance(value);
+
+ List<V> set = get(key);
+ if ( set == null) {
+ return false;
+ }
+ boolean result = set.remove(value);
+ if ( set.isEmpty())
+ remove(key);
+ return result;
+ }
+
+ public boolean removeAll( K key, Collection<V> value ) {
+ assert keyClass.isInstance(key);
+ List<V> set = get(key);
+ if ( set == null) {
+ return false;
+ }
+ boolean result = set.removeAll(value);
+ if ( set.isEmpty())
+ remove(key);
+ return result;
+ }
+
+ public Iterator<V> iterate(K key) {
+ assert keyClass.isInstance(key);
+ List<V> set = get(key);
+ if ( set == null)
+ return EMPTY.iterator();
+ return set.iterator();
+ }
+
+ public Iterator<V> all() {
+ return new Iterator<V>() {
+ Iterator<List<V>> master = values().iterator();
+ Iterator<V> current = null;
+
+ public boolean hasNext() {
+ if ( current == null || !current.hasNext()) {
+ if ( master.hasNext()) {
+ current = master.next().iterator();
+ return current.hasNext();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public V next() {
+ return current.next();
+ }
+
+ public void remove() {
+ current.remove();
+ }
+
+ };
+ }
+
+ public Map<K,V> flatten() {
+ Map<K,V> map = new LinkedHashMap<K,V>();
+ for ( Map.Entry<K, List<V>> entry : entrySet()) {
+ List<V> v = entry.getValue();
+ if ( v == null || v.isEmpty())
+ continue;
+
+ map.put(entry.getKey(), v.get(0));
+ }
+ return map;
+ }
+
+ public MultiMap<V,K> transpose() {
+ MultiMap<V,K> inverted = new MultiMap<V,K>();
+ for ( Map.Entry<K, List<V>> entry : entrySet()) {
+ K key = entry.getKey();
+
+ List<V> value = entry.getValue();
+ if ( value == null)
+ continue;
+
+ for ( V v : value)
+ inverted.add(v, key);
+ }
+
+ return inverted;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java b/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
new file mode 100644
index 0000000..220d875
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
@@ -0,0 +1,423 @@
+package aQute.lib.collections;
+
+import java.util.*;
+
+/**
+ * An immutbale list that sorts objects by their natural order or through a
+ * comparator. It has convenient methods/constructors to create it from
+ * collections and iterators.
+ *
+ * Why not maintain the lists in their sorted form? Well, TreeMaps are quite
+ * expensive ... I once profiled bnd and was shocked how much memory the Jar
+ * class took due to the TreeMaps. I could not easily change it unfortunately.
+ * The other reason is that Parameters uses a LinkedHashMap because the
+ * preferred order should be the declaration order. However, sometimes you need
+ * to sort the keys by name.
+ *
+ * Last, and most important reason, is that sometimes you do not know what
+ * collection you have or it is not available in a sort ordering (MultiMap for
+ * example) ... I found myself sorting these things over and over again and
+ * decided to just make an immutable SortedList that is easy to slice and dice
+ *
+ * @param <T>
+ */
+@SuppressWarnings("unchecked") public class SortedList<T> implements SortedSet<T>, List<T> {
+ static SortedList<?> empty = new SortedList<Object>();
+
+ final T[] list;
+ final int start;
+ final int end;
+ final Comparator<T> cmp;
+ Class<?> type;
+ static Comparator<Object> comparator = //
+
+ new Comparator<Object>() {
+ public int compare(Object o1, Object o2) {
+
+ if (o1 == o2)
+ return 0;
+
+ if (o1.equals(o2))
+ return 0;
+
+ return ((Comparable<Object>) o1).compareTo(o2);
+ }
+ };
+
+ class It implements ListIterator<T> {
+ int n;
+
+ private It(int n) {
+ this.n = n;
+ }
+
+ public boolean hasNext() {
+ return n < end;
+ }
+
+ public T next() throws NoSuchElementException {
+ if (!hasNext()) {
+ throw new NoSuchElementException("");
+ }
+ return list[n++];
+ }
+
+ public boolean hasPrevious() {
+ return n > start;
+ }
+
+ public T previous() {
+ return get(n - 1);
+ }
+
+ public int nextIndex() {
+ return (n + 1 - start);
+ }
+
+ public int previousIndex() {
+ return (n - 1) - start;
+ }
+
+ @Deprecated public void remove() {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ @Deprecated public void set(T e) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ @Deprecated public void add(T e) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+ }
+
+ public SortedList(Collection<? extends Comparable<?>> x) {
+ this((Collection<T>) x, 0, x.size(), (Comparator<T>) comparator);
+ }
+
+ public SortedList(Collection<T> x, Comparator<T> cmp) {
+ this(x, 0, x.size(), cmp);
+ }
+
+ @SuppressWarnings("cast")
+ public SortedList(T... x) {
+ this((T[]) x.clone(), 0, x.length, (Comparator<T>) comparator);
+ }
+
+ @SuppressWarnings("cast")
+ public SortedList(Comparator<T> cmp, T... x) {
+ this((T[]) x.clone(), 0, x.length, cmp);
+ }
+
+ private SortedList(SortedList<T> other, int start, int end) {
+ this.list = other.list;
+ this.cmp = other.cmp;
+ this.start = start;
+ this.end = end;
+ }
+
+ public SortedList(T[] x, int start, int end, Comparator<T> comparator2) {
+ if (start > end) {
+ int tmp = start;
+ start = end;
+ end = tmp;
+ }
+ if (start < 0 || start >= x.length)
+ throw new IllegalArgumentException("Start is not in list");
+
+ if (end < 0 || end > x.length)
+ throw new IllegalArgumentException("End is not in list");
+
+ this.list = x.clone();
+ Arrays.sort(this.list, start, end, comparator2);
+ this.start = start;
+ this.end = end;
+ this.cmp = comparator2;
+ }
+
+ public SortedList(Collection<? extends T> x, int start, int end, Comparator<T> cmp) {
+ if (start > end) {
+ int tmp = start;
+ start = end;
+ end = tmp;
+ }
+ if (start < 0 || start > x.size())
+ throw new IllegalArgumentException("Start is not in list");
+
+ if (end < 0 || end > x.size())
+ throw new IllegalArgumentException("End is not in list");
+
+ this.list = (T[]) x.toArray();
+ Arrays.sort(this.list, start, end, cmp);
+ this.start = start;
+ this.end = end;
+ this.cmp = cmp;
+ }
+
+ private SortedList() {
+ list = null;
+ start = 0;
+ end = 0;
+ cmp = null;
+ }
+
+ public int size() {
+ return end - start;
+ }
+
+ public boolean isEmpty() {
+ return start == end;
+ }
+
+ @SuppressWarnings("cast")
+ public boolean contains(Object o) {
+ assert type != null & type.isInstance(o);
+ return indexOf((T) o) >= 0;
+ }
+
+ public Iterator<T> iterator() {
+ return new It(start);
+ }
+
+ public Object[] toArray() {
+ return list.clone();
+ }
+
+ @SuppressWarnings("hiding") public <T> T[] toArray(T[] a) {
+ if (a == null || a.length < list.length) {
+ return (T[]) list.clone();
+ }
+ System.arraycopy(list, 0, a, 0, list.length);
+ return a;
+ }
+
+ public boolean add(T e) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public boolean containsAll(Collection<?> c) {
+ if (c.isEmpty())
+ return true;
+
+ if (isEmpty())
+ return false;
+
+ // TODO take advantage of sorted nature for this
+
+ for (Object el : c) {
+ if (!contains(el))
+ return false;
+ }
+ return false;
+ }
+
+ public boolean addAll(Collection<? extends T> c) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public boolean retainAll(Collection<?> c) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public boolean removeAll(Collection<?> c) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public Comparator<? super T> comparator() {
+ return cmp;
+ }
+
+ public boolean isSubSet() {
+ return start > 0 && end < list.length;
+ }
+
+ public SortedList<T> subSet(T fromElement, T toElement) {
+ int start = indexOf(fromElement);
+ int end = indexOf(toElement);
+ if (isSubSet() && (start < 0 || end < 0))
+ throw new IllegalArgumentException("This list is a subset");
+ if (start < 0)
+ start = 0;
+ if (end < 0)
+ end = list.length;
+
+ return subList(start, end);
+ }
+
+ public int indexOf(Object o) {
+ assert type != null && type.isInstance(o);
+
+ int n = Arrays.binarySearch(list, (T) o, cmp);
+ if (n >= start && n < end)
+ return n - start;
+
+ return -1;
+ }
+
+ public SortedList<T> headSet(T toElement) {
+ int i = indexOf(toElement);
+ if (i < 0) {
+ if (isSubSet())
+ throw new IllegalArgumentException("This list is a subset");
+ i = end;
+ }
+
+ if (i == end)
+ return this;
+
+ return subList(0, i);
+ }
+
+ public SortedSet<T> tailSet(T fromElement) {
+ int i = indexOf(fromElement);
+ if (i < 0) {
+ if (isSubSet())
+ throw new IllegalArgumentException("This list is a subset");
+ i = start;
+ }
+
+ return subList(i, end);
+ }
+
+ public T first() {
+ if (isEmpty())
+ throw new NoSuchElementException("first");
+ return get(0);
+ }
+
+ public T last() {
+ if (isEmpty())
+ throw new NoSuchElementException("last");
+ return get(end - 1);
+ }
+
+ @Deprecated public boolean addAll(int index, Collection<? extends T> c) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public T get(int index) {
+ return list[index + start];
+ }
+
+ @Deprecated public T set(int index, T element) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ @Deprecated public void add(int index, T element) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ @Deprecated public T remove(int index) {
+ throw new UnsupportedOperationException("Immutable");
+ }
+
+ public int lastIndexOf(Object o) {
+ int n = indexOf(o);
+ if (n < 0)
+ return -1;
+
+ while (cmp.compare(list[n], (T) o) == 0)
+ n++;
+
+ return n;
+ }
+
+ public ListIterator<T> listIterator() {
+ return new It(start);
+ }
+
+ public ListIterator<T> listIterator(int index) {
+ return new It(index + start);
+ }
+
+ public SortedList<T> subList(int fromIndex, int toIndex) {
+ fromIndex += start;
+ toIndex += start;
+
+ if (toIndex < fromIndex) {
+ int tmp = toIndex;
+ toIndex = fromIndex;
+ fromIndex = tmp;
+ }
+
+ toIndex = Math.max(0, toIndex);
+ toIndex = Math.min(toIndex, end);
+ fromIndex = Math.max(0, fromIndex);
+ fromIndex = Math.min(fromIndex, end);
+ if (fromIndex == start && toIndex == end)
+ return this;
+
+ return new SortedList<T>(this, fromIndex, toIndex);
+ }
+
+ @Deprecated public boolean equals(Object other) {
+ return super.equals(other);
+ }
+
+ @Deprecated public int hashCode() {
+ return super.hashCode();
+ }
+
+ public boolean isEqual(SortedList<T> list) {
+ if (size() != list.size())
+ return false;
+
+ for (int as = start, bs = list.start, al = size(); as < al && bs < al; as++, bs++) {
+ if (comparator.compare(this.list[as], this.list[bs]) != 0)
+ return false;
+ }
+ return true;
+ }
+
+ public Class<?> getType() {
+ return type;
+ }
+
+ public void setType(Class<?> type) {
+ this.type = type;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ String del = "";
+ for (T s : list) {
+ sb.append(del);
+ sb.append(s);
+ del = ", ";
+ }
+
+ sb.append("]");
+ return sb.toString();
+ }
+
+ public boolean hasDuplicates() {
+ if (list.length < 2)
+ return false;
+
+ T prev = list[0];
+ for (int i = 1; i < list.length; i++) {
+ if (prev.equals(list[i]))
+ return true;
+ }
+ return false;
+ }
+
+ public static <T extends Comparable<?>> SortedList<T> fromIterator(Iterator<T> it) {
+ IteratorList<T> l = new IteratorList<T>(it);
+ return new SortedList<T>(l);
+ }
+
+ public static <T> SortedList<T> fromIterator(Iterator<T> it, Comparator<T> cmp) {
+ IteratorList<T> l = new IteratorList<T>(it);
+ return new SortedList<T>(l, cmp);
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/packageinfo b/bundleplugin/src/main/java/aQute/lib/collections/packageinfo
new file mode 100644
index 0000000..3987f9c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/collections/packageinfo
@@ -0,0 +1 @@
+version 1.1
diff --git a/bundleplugin/src/main/java/aQute/lib/converter/Converter.java b/bundleplugin/src/main/java/aQute/lib/converter/Converter.java
new file mode 100644
index 0000000..abbbf9b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/converter/Converter.java
@@ -0,0 +1,364 @@
+package aQute.lib.converter;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.regex.*;
+
+import aQute.lib.base64.*;
+
+/**
+ * General Java type converter from an object to any type. Supports number
+ * conversion
+ *
+ * @author aqute
+ *
+ */
+@SuppressWarnings({ "unchecked", "rawtypes" }) public class Converter {
+ boolean fatal = true;
+
+ public <T> T convert(Class<T> type, Object o) throws Exception {
+ return (T) convert((Type) type, o);
+ }
+
+ public Object convert(Type type, Object o) throws Exception {
+ if (o == null)
+ return null; // compatible with any
+
+ Class resultType = getRawClass(type);
+ Class<?> actualType = o.getClass();
+ // Is it a compatible type?
+ if (resultType.isAssignableFrom(actualType))
+ return o;
+
+ // We can always make a string
+
+ if (resultType == String.class) {
+ if (actualType.isArray()) {
+ if (actualType == char[].class)
+ return new String((char[]) o);
+ if (actualType == byte[].class)
+ return Base64.encodeBase64((byte[]) o);
+ int l = Array.getLength(o);
+ StringBuilder sb = new StringBuilder("[");
+ String del = "";
+ for (int i = 0; i < l; i++) {
+ sb.append(del);
+ del = ",";
+ sb.append(convert(String.class, Array.get(o, i)));
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+ return o.toString();
+ }
+
+ if (Collection.class.isAssignableFrom(resultType))
+ return collection(type, resultType, o);
+
+ if (Map.class.isAssignableFrom(resultType))
+ return map(type, resultType, o);
+
+ if (type instanceof GenericArrayType) {
+ GenericArrayType gType = (GenericArrayType) type;
+ return array(gType.getGenericComponentType(), o);
+ }
+
+ if (resultType.isArray()) {
+ if (actualType == String.class) {
+ String s = (String) o;
+ if (byte[].class == resultType)
+ return Base64.decodeBase64(s);
+
+ if (char[].class == resultType)
+ return s.toCharArray();
+ }
+ if (byte[].class == resultType) {
+ // Sometimes classes implement toByteArray
+ try {
+ Method m = actualType.getMethod("toByteArray");
+ if (m.getReturnType() == byte[].class)
+ return m.invoke(o);
+
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+
+ return array(resultType.getComponentType(), o);
+ }
+
+ // Simple type coercion
+
+ if (resultType == boolean.class || resultType == Boolean.class) {
+ if (actualType == boolean.class || actualType == Boolean.class)
+ return o;
+ Number n = number(o);
+ if (n != null)
+ return n.longValue() == 0 ? false : true;
+
+ resultType = Boolean.class;
+ } else if (resultType == byte.class || resultType == Byte.class) {
+ Number n = number(o);
+ if (n != null)
+ return n.byteValue();
+ resultType = Byte.class;
+ } else if (resultType == char.class || resultType == Character.class) {
+ Number n = number(o);
+ if (n != null)
+ return (char) n.shortValue();
+ resultType = Character.class;
+ } else if (resultType == short.class || resultType == Short.class) {
+ Number n = number(o);
+ if (n != null)
+ return n.shortValue();
+
+ resultType = Short.class;
+ } else if (resultType == int.class || resultType == Integer.class) {
+ Number n = number(o);
+ if (n != null)
+ return n.intValue();
+
+ resultType = Integer.class;
+ } else if (resultType == long.class || resultType == Long.class) {
+ Number n = number(o);
+ if (n != null)
+ return n.longValue();
+
+ resultType = Long.class;
+ } else if (resultType == float.class || resultType == Float.class) {
+ Number n = number(o);
+ if (n != null)
+ return n.floatValue();
+
+ resultType = Float.class;
+ } else if (resultType == double.class || resultType == Double.class) {
+ Number n = number(o);
+ if (n != null)
+ return n.doubleValue();
+
+ resultType = Double.class;
+ }
+
+ assert !resultType.isPrimitive();
+
+ if (actualType == String.class) {
+ String input = (String) o;
+ if (resultType == char[].class)
+ return input.toCharArray();
+
+ if (resultType == byte[].class)
+ return Base64.decodeBase64(input);
+
+ if (Enum.class.isAssignableFrom(resultType)) {
+ return Enum.valueOf((Class<Enum>) resultType, input);
+ }
+ if (resultType == Pattern.class) {
+ return Pattern.compile(input);
+ }
+
+ try {
+ Constructor<?> c = resultType.getConstructor(String.class);
+ return c.newInstance(o.toString());
+ } catch (Throwable t) {
+ }
+ try {
+ Method m = resultType.getMethod("valueOf", String.class);
+ if (Modifier.isStatic(m.getModifiers()))
+ return m.invoke(null, o.toString());
+ } catch (Throwable t) {
+ }
+
+ if (resultType == Character.class && input.length() == 1)
+ return input.charAt(0);
+ }
+ Number n = number(o);
+ if (n != null) {
+ if (Enum.class.isAssignableFrom(resultType)) {
+ try {
+ Method values = resultType.getMethod("values");
+ Enum[] vs = (Enum[]) values.invoke(null);
+ int nn = n.intValue();
+ if (nn > 0 && nn < vs.length)
+ return vs[nn];
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+ return error("No conversion found for " + o.getClass() + " to " + type);
+ }
+
+ private Number number(Object o) {
+ if (o instanceof Number)
+ return (Number) o;
+
+ if (o instanceof Boolean)
+ return ((Boolean) o).booleanValue() ? 1 : 0;
+
+ if (o instanceof Character)
+ return (int) ((Character) o).charValue();
+
+ if (o instanceof String) {
+ String s = (String) o;
+ try {
+ return Double.parseDouble(s);
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ return null;
+ }
+
+ private Collection collection(Type collectionType, Class<? extends Collection> rawClass,
+ Object o) throws Exception {
+ Collection collection;
+ if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
+ if (rawClass.isAssignableFrom(ArrayList.class))
+ collection = new ArrayList();
+ else if (rawClass.isAssignableFrom(HashSet.class))
+ collection = new HashSet();
+ else if (rawClass.isAssignableFrom(TreeSet.class))
+ collection = new TreeSet();
+ else if (rawClass.isAssignableFrom(LinkedList.class))
+ collection = new LinkedList();
+ else if (rawClass.isAssignableFrom(Vector.class))
+ collection = new Vector();
+ else if (rawClass.isAssignableFrom(Stack.class))
+ collection = new Stack();
+ else if (rawClass.isAssignableFrom(ConcurrentLinkedQueue.class))
+ collection = new ConcurrentLinkedQueue();
+ else
+ return (Collection) error("Cannot find a suitable collection for the collection interface "
+ + rawClass);
+ } else
+ collection = rawClass.newInstance();
+
+ Type subType = Object.class;
+ if (collectionType instanceof ParameterizedType) {
+ ParameterizedType ptype = (ParameterizedType) collectionType;
+ subType = ptype.getActualTypeArguments()[0];
+ }
+
+ Collection input = toCollection(o);
+
+ for (Object i : input)
+ collection.add(convert(subType, i));
+
+ return collection;
+ }
+
+ private Map map(Type mapType, Class<? extends Map<?, ?>> rawClass, Object o) throws Exception {
+ Map result;
+ if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
+ if (rawClass.isAssignableFrom(HashMap.class))
+ result = new HashMap();
+ else if (rawClass.isAssignableFrom(TreeMap.class))
+ result = new TreeMap();
+ else if (rawClass.isAssignableFrom(ConcurrentHashMap.class))
+ result = new ConcurrentHashMap();
+ else
+ return (Map) error("Cannot find suitable map for map interface " + rawClass);
+ } else
+ result = rawClass.newInstance();
+
+ Map<?, ?> input = toMap(o);
+
+ Type keyType = Object.class;
+ Type valueType = Object.class;
+ if (mapType instanceof ParameterizedType) {
+ ParameterizedType ptype = (ParameterizedType) mapType;
+ keyType = ptype.getActualTypeArguments()[0];
+ valueType = ptype.getActualTypeArguments()[1];
+ }
+
+ for (Map.Entry<?, ?> entry : input.entrySet()) {
+ Object key = convert(keyType, entry.getKey());
+ Object value = convert(valueType, entry.getValue());
+ if (value == null)
+ return (Map) error("Key for map must not be null");
+ result.put(key, value);
+ }
+
+ return result;
+ }
+
+ public Object array(Type type, Object o) throws Exception {
+ Collection<?> input = toCollection(o);
+ Class<?> componentClass = getRawClass(type);
+ Object array = Array.newInstance(componentClass, input.size());
+
+ int i = 0;
+ for (Object next : input) {
+ Array.set(array, i++, convert(type, next));
+ }
+ return array;
+ }
+
+ private Class<?> getRawClass(Type type) {
+ if (type instanceof Class)
+ return (Class<?>) type;
+
+ if (type instanceof ParameterizedType)
+ return (Class<?>) ((ParameterizedType) type).getRawType();
+
+ if (type instanceof GenericArrayType) {
+ Type componentType = ((GenericArrayType) type).getGenericComponentType();
+ return Array.newInstance(getRawClass(componentType), 0).getClass();
+ }
+
+ if (type instanceof TypeVariable) {
+ Type componentType = ((TypeVariable) type).getBounds()[0];
+ return Array.newInstance(getRawClass(componentType), 0).getClass();
+ }
+
+ if (type instanceof WildcardType) {
+ Type componentType = ((WildcardType) type).getUpperBounds()[0];
+ return Array.newInstance(getRawClass(componentType), 0).getClass();
+ }
+
+ return Object.class;
+ }
+
+ public Collection<?> toCollection(Object o) {
+ if (o instanceof Collection)
+ return (Collection<?>) o;
+
+ if (o.getClass().isArray()) {
+ if (o.getClass().getComponentType().isPrimitive()) {
+ int length = Array.getLength(o);
+ List<Object> result = new ArrayList<Object>(length);
+ for (int i = 0; i < length; i++) {
+ result.add(Array.get(o, i));
+ }
+ return result;
+ }
+ return Arrays.asList((Object[]) o);
+ }
+
+ return Arrays.asList(o);
+ }
+
+ public Map<?, ?> toMap(Object o) throws Exception {
+ if (o instanceof Map)
+ return (Map<?, ?>) o;
+ Map result = new HashMap();
+ Field fields[] = o.getClass().getFields();
+ for (Field f : fields)
+ result.put(f.getName(), f.get(o));
+ if (result.isEmpty())
+ return null;
+
+ return result;
+ }
+
+ private Object error(String string) {
+ if (fatal)
+ throw new IllegalArgumentException(string);
+ return null;
+ }
+
+ public void setFatalIsException(boolean b) {
+ fatal = b;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/converter/packageinfo b/bundleplugin/src/main/java/aQute/lib/converter/packageinfo
new file mode 100644
index 0000000..3390555
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/converter/packageinfo
@@ -0,0 +1 @@
+version 2.0.1
diff --git a/bundleplugin/src/main/java/aQute/lib/data/AllowNull.java b/bundleplugin/src/main/java/aQute/lib/data/AllowNull.java
new file mode 100644
index 0000000..2bbf253
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/data/AllowNull.java
@@ -0,0 +1,9 @@
+package aQute.lib.data;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value={ElementType.FIELD})
+public @interface AllowNull {
+ String reason() default "";
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/data/Data.java b/bundleplugin/src/main/java/aQute/lib/data/Data.java
new file mode 100644
index 0000000..d790d2e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/data/Data.java
@@ -0,0 +1,79 @@
+package aQute.lib.data;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+public class Data {
+
+ public static String validate(Object o) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ Formatter formatter = new Formatter(sb);
+
+ Field fields[] = o.getClass().getFields();
+ for (Field f : fields) {
+ Validator patternValidator = f.getAnnotation(Validator.class);
+ Numeric numericValidator = f.getAnnotation(Numeric.class);
+ AllowNull allowNull = f.getAnnotation(AllowNull.class);
+ Object value = f.get(o);
+ if (value == null) {
+ if (allowNull == null)
+ formatter.format("Value for %s must not be null\n", f.getName());
+ } else {
+
+
+ if (patternValidator != null) {
+ Pattern p = Pattern.compile(patternValidator.value());
+ Matcher m = p.matcher(value.toString());
+ if (!m.matches()) {
+ String reason = patternValidator.reason();
+ if (reason.length() == 0)
+ formatter.format("Value for %s=%s does not match pattern %s\n",
+ f.getName(), value, patternValidator.value());
+ else
+ formatter.format("Value for %s=%s %s\n", f.getName(), value, reason);
+ }
+ }
+
+ if (numericValidator != null) {
+ if (o instanceof String) {
+ try {
+ o = Double.parseDouble((String) o);
+ } catch (Exception e) {
+ formatter.format("Value for %s=%s %s\n", f.getName(), value, "Not a number");
+ }
+ }
+
+ try {
+ Number n = (Number) o;
+ long number = n.longValue();
+ if (number >= numericValidator.min() && number < numericValidator.max()) {
+ formatter.format("Value for %s=%s not in valid range (%s,%s]\n",
+ f.getName(), value, numericValidator.min(), numericValidator.max());
+ }
+ } catch (ClassCastException e) {
+ formatter.format("Value for %s=%s [%s,%s) is not a number\n", f.getName(), value,
+ numericValidator.min(), numericValidator.max());
+ }
+ }
+ }
+ }
+ if ( sb.length() == 0)
+ return null;
+
+ if ( sb.length() > 0)
+ sb.delete(sb.length() - 1, sb.length());
+ return sb.toString();
+ }
+
+ public static void details(Object data, Appendable out) throws Exception {
+ Field fields[] = data.getClass().getFields();
+ Formatter formatter = new Formatter(out);
+
+ for ( Field f : fields ) {
+ String name = f.getName();
+ name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ formatter.format("%-40s %s\n", name, f.get(data));
+ }
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/data/Numeric.java b/bundleplugin/src/main/java/aQute/lib/data/Numeric.java
new file mode 100644
index 0000000..19d60cf
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/data/Numeric.java
@@ -0,0 +1,11 @@
+package aQute.lib.data;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value={ElementType.FIELD})
+public @interface Numeric {
+ long min() default Long.MIN_VALUE;
+ long max() default Long.MAX_VALUE;
+ String reason() default "";
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/data/Validator.java b/bundleplugin/src/main/java/aQute/lib/data/Validator.java
new file mode 100644
index 0000000..e8a8d4f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/data/Validator.java
@@ -0,0 +1,10 @@
+package aQute.lib.data;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value={ElementType.FIELD})
+public @interface Validator {
+ String value();
+ String reason() default "";
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/data/packageinfo b/bundleplugin/src/main/java/aQute/lib/data/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/data/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
diff --git a/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java b/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java
new file mode 100644
index 0000000..780c32f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java
@@ -0,0 +1,150 @@
+package aQute.lib.deployer;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.osgi.*;
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class FileInstallRepo extends FileRepo {
+
+ String group;
+ boolean dirty;
+ Reporter reporter;
+ Pattern REPO_FILE = Pattern
+ .compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+)\\.(jar|lib)");
+
+ public void setProperties(Map<String, String> map) {
+ super.setProperties(map);
+ group = map.get("group");
+ }
+ public void setReporter(Reporter reporter) {
+ super.setReporter(reporter);
+ this.reporter = reporter;
+ }
+
+ public File put(Jar jar) throws Exception {
+ dirty = true;
+ Manifest manifest = jar.getManifest();
+ if (manifest == null)
+ throw new IllegalArgumentException("No manifest in JAR: " + jar);
+
+ String bsn = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_SYMBOLICNAME);
+ if (bsn == null)
+ throw new IllegalArgumentException("No Bundle SymbolicName set");
+
+ Parameters b = Processor.parseHeader(bsn, null);
+ if (b.size() != 1)
+ throw new IllegalArgumentException("Multiple bsn's specified " + b);
+
+ for (String key : b.keySet()) {
+ bsn = key;
+ if (!Verifier.SYMBOLICNAME.matcher(bsn).matches())
+ throw new IllegalArgumentException(
+ "Bundle SymbolicName has wrong format: " + bsn);
+ }
+
+ String versionString = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_VERSION);
+ Version version;
+ if (versionString == null)
+ version = new Version();
+ else
+ version = new Version(versionString);
+
+ File dir;
+ if (group == null) {
+ dir = getRoot();
+ } else {
+ dir= new File(getRoot(), group);
+ dir.mkdirs();
+ }
+ String fName = bsn + "-" + version.getMajor() + "."
+ + version.getMinor() + "." + version.getMicro() + ".jar";
+ File file = new File(dir, fName);
+
+ jar.write(file);
+ fireBundleAdded(jar, file);
+
+ file = new File(dir, bsn + "-latest.jar");
+ if (file.isFile() && file.lastModified() < jar.lastModified()) {
+ jar.write(file);
+ }
+ return file;
+ }
+ public boolean refresh() {
+ if ( dirty ) {
+ dirty = false;
+ return true;
+ } else
+ return false;
+ }
+ @Override
+ public List<String> list(String regex) {
+ Instruction pattern = null;
+ if (regex != null)
+ pattern = new Instruction(regex);
+
+ String list[] = getRoot().list();
+ List<String> result = new ArrayList<String>();
+ for (String f : list) {
+ Matcher m = REPO_FILE.matcher(f);
+ if (!m.matches()) {
+ continue;
+ }
+ String s = m.group(1);
+ if (pattern == null || pattern.matches(s))
+ result.add(s);
+ }
+ return result;
+ }
+ @Override
+ public File[] get(String bsn, String versionRange) throws MalformedURLException {
+ // If the version is set to project, we assume it is not
+ // for us. A project repo will then get it.
+ if (versionRange != null && versionRange.equals("project"))
+ return null;
+
+ //
+ // The version range we are looking for can
+ // be null (for all) or a version range.
+ //
+ VersionRange range;
+ if (versionRange == null || versionRange.equals("latest")) {
+ range = new VersionRange("0");
+ } else
+ range = new VersionRange(versionRange);
+
+ //
+ // 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.
+ //
+ File instances[] = getRoot().listFiles();
+ SortedMap<Version, File> versions = new TreeMap<Version, File>();
+ for (int i = 0; i < instances.length; i++) {
+ Matcher m = REPO_FILE.matcher(instances[i].getName());
+ if (m.matches() && m.group(1).equals(bsn)) {
+ String versionString = m.group(2);
+ Version version;
+ if (versionString.equals("latest"))
+ version = new Version(Integer.MAX_VALUE);
+ else
+ version = new Version(versionString);
+
+ if (range.includes(version))
+ versions.put(version, instances[i]);
+ }
+ }
+ return versions.values().toArray(new File[versions.size()]);
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java b/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
new file mode 100644
index 0000000..8cd39f1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
@@ -0,0 +1,333 @@
+package aQute.lib.deployer;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class FileRepo implements Plugin, RepositoryPlugin, Refreshable, RegistryPlugin {
+ public final static String LOCATION = "location";
+ public final static String READONLY = "readonly";
+ public final static String NAME = "name";
+
+ File[] EMPTY_FILES = new File[0];
+ protected File root;
+ Registry registry;
+ boolean canWrite = true;
+ Pattern REPO_FILE = Pattern
+ .compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+|latest)\\.(jar|lib)");
+ Reporter reporter;
+ boolean dirty;
+ String name;
+
+ public FileRepo() {
+ }
+
+ public FileRepo(String name, File location, boolean canWrite) {
+ this.name = name;
+ this.root = location;
+ this.canWrite = canWrite;
+ }
+
+ protected void init() throws Exception {
+ // for extensions
+ }
+
+ public void setProperties(Map<String, String> map) {
+ String location = map.get(LOCATION);
+ if (location == null)
+ throw new IllegalArgumentException("Location must be set on a FileRepo plugin");
+
+ root = new File(location);
+ if (!root.isDirectory())
+ throw new IllegalArgumentException("Repository is not a valid directory " + root);
+
+ String readonly = map.get(READONLY);
+ if (readonly != null && Boolean.valueOf(readonly).booleanValue())
+ canWrite = false;
+
+ name = map.get(NAME);
+ }
+
+ /**
+ * Get a list of URLs to bundles that are constrained by the bsn and
+ * versionRange.
+ */
+ public File[] get(String bsn, String versionRange) throws Exception {
+ init();
+
+ // If the version is set to project, we assume it is not
+ // for us. A project repo will then get it.
+ if (versionRange != null && versionRange.equals("project"))
+ return null;
+
+ //
+ // Check if the entry exists
+ //
+ File f = new File(root, bsn);
+ if (!f.isDirectory())
+ return null;
+
+ //
+ // The version range we are looking for can
+ // be null (for all) or a version range.
+ //
+ VersionRange range;
+ if (versionRange == null || versionRange.equals("latest")) {
+ range = new VersionRange("0");
+ } else
+ range = new VersionRange(versionRange);
+
+ //
+ // 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.
+ //
+ File instances[] = f.listFiles();
+ SortedMap<Version, File> versions = new TreeMap<Version, File>();
+ for (int i = 0; i < instances.length; i++) {
+ Matcher m = REPO_FILE.matcher(instances[i].getName());
+ if (m.matches() && m.group(1).equals(bsn)) {
+ String versionString = m.group(2);
+ Version version;
+ if (versionString.equals("latest"))
+ version = new Version(Integer.MAX_VALUE);
+ else
+ version = new Version(versionString);
+
+ if (range.includes(version) || versionString.equals(versionRange))
+ versions.put(version, instances[i]);
+ }
+ }
+
+ File[] files = versions.values().toArray(EMPTY_FILES);
+ if ("latest".equals(versionRange) && files.length > 0) {
+ return new File[] { files[files.length - 1] };
+ }
+ return files;
+ }
+
+ public boolean canWrite() {
+ return canWrite;
+ }
+
+ public File put(Jar jar) throws Exception {
+ init();
+ dirty = true;
+
+ Manifest manifest = jar.getManifest();
+ if (manifest == null)
+ throw new IllegalArgumentException("No manifest in JAR: " + jar);
+
+ String bsn = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_SYMBOLICNAME);
+ if (bsn == null)
+ throw new IllegalArgumentException("No Bundle SymbolicName set");
+
+ Parameters b = Processor.parseHeader(bsn, null);
+ if (b.size() != 1)
+ throw new IllegalArgumentException("Multiple bsn's specified " + b);
+
+ for (String key : b.keySet()) {
+ bsn = key;
+ if (!Verifier.SYMBOLICNAME.matcher(bsn).matches())
+ throw new IllegalArgumentException("Bundle SymbolicName has wrong format: " + bsn);
+ }
+
+ String versionString = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_VERSION);
+ Version version;
+ if (versionString == null)
+ version = new Version();
+ else
+ version = new Version(versionString);
+
+ reporter.trace("bsn=%s version=%s",bsn,version);
+
+ File dir = new File(root, bsn);
+ dir.mkdirs();
+ String fName = bsn + "-" + version.getWithoutQualifier() + ".jar";
+ File file = new File(dir, fName);
+
+ reporter.trace("updating %s ", file.getAbsolutePath());
+ if (!file.exists() || file.lastModified() < jar.lastModified()) {
+ jar.write(file);
+ reporter.progress("updated " + file.getAbsolutePath());
+ fireBundleAdded(jar, file);
+ } else {
+ reporter.progress("Did not update " + jar + " because repo has a newer version");
+ reporter.trace("NOT Updating " + fName + " (repo is newer)");
+ }
+
+ File latest = new File(dir, bsn + "-latest.jar");
+ if (latest.exists() && latest.lastModified() < jar.lastModified()) {
+ jar.write(latest);
+ file = latest;
+ }
+
+ return file;
+ }
+
+ protected void fireBundleAdded(Jar jar, File file) {
+ if (registry == null)
+ return;
+ List<RepositoryListenerPlugin> listeners = registry
+ .getPlugins(RepositoryListenerPlugin.class);
+ for (RepositoryListenerPlugin listener : listeners) {
+ try {
+ listener.bundleAdded(this, jar, file);
+ } catch (Exception e) {
+ if (reporter != null)
+ reporter.warning("Repository listener threw an unexpected exception: %s", e);
+ }
+ }
+ }
+
+ public void setLocation(String string) {
+ root = new File(string);
+ if (!root.isDirectory())
+ throw new IllegalArgumentException("Invalid repository directory");
+ }
+
+ public void setReporter(Reporter reporter) {
+ this.reporter = reporter;
+ }
+
+ public List<String> list(String regex) throws Exception {
+ init();
+ Instruction pattern = null;
+ if (regex != null)
+ pattern = new Instruction(regex);
+
+ List<String> result = new ArrayList<String>();
+ if (root == null) {
+ if (reporter != null)
+ reporter.error("FileRepo root directory is not set.");
+ } else {
+ File[] list = root.listFiles();
+ if (list != null) {
+ for (File f : list) {
+ if (!f.isDirectory())
+ continue; // ignore non-directories
+ String fileName = f.getName();
+ if (fileName.charAt(0) == '.')
+ continue; // ignore hidden files
+ if (pattern == null || pattern.matches(fileName))
+ result.add(fileName);
+ }
+ } else if (reporter != null)
+ reporter.error("FileRepo root directory (%s) does not exist", root);
+ }
+
+ return result;
+ }
+
+ public List<Version> versions(String bsn) throws Exception {
+ init();
+ File dir = new File(root, bsn);
+ if (dir.isDirectory()) {
+ String versions[] = dir.list();
+ List<Version> list = new ArrayList<Version>();
+ for (String v : versions) {
+ Matcher m = REPO_FILE.matcher(v);
+ if (m.matches()) {
+ String version = m.group(2);
+ if (version.equals("latest"))
+ version = Integer.MAX_VALUE + "";
+ list.add(new Version(version));
+ }
+ }
+ return list;
+ }
+ return null;
+ }
+
+ public String toString() {
+ return String.format("%-40s r/w=%s", root.getAbsolutePath(), canWrite());
+ }
+
+ public File getRoot() {
+ return root;
+ }
+
+ public boolean refresh() {
+ if (dirty) {
+ dirty = false;
+ return true;
+ } else
+ return false;
+ }
+
+ public String getName() {
+ if (name == null) {
+ return toString();
+ }
+ return name;
+ }
+
+ public Jar get(String bsn, Version v) throws Exception {
+ init();
+ File bsns = new File(root, bsn);
+ File version = new File(bsns, bsn + "-" + v.getMajor() + "." + v.getMinor() + "."
+ + v.getMicro() + ".jar");
+ if ( version.exists())
+ return new Jar(version);
+ else
+ return null;
+ }
+
+ public File get(String bsn, String version, Strategy strategy, Map<String, String> properties)
+ throws Exception {
+ if (version == null)
+ version = "0.0.0";
+
+ if (strategy == Strategy.EXACT) {
+ VersionRange vr = new VersionRange(version);
+ if (vr.isRange())
+ return null;
+
+ if (vr.getHigh().getMajor() == Integer.MAX_VALUE)
+ version = "latest";
+
+ File file = IO.getFile(root, bsn + "/" + bsn + "-" + version + ".jar");
+ if (file.isFile())
+ return file;
+ else {
+ file = IO.getFile(root, bsn + "/" + bsn + "-" + version + ".lib");
+ if (file.isFile())
+ return file;
+ }
+ return null;
+
+ }
+ File[] files = get(bsn, version);
+ if (files == null || files.length == 0)
+ return null;
+
+ if (files.length >= 0) {
+ switch (strategy) {
+ case LOWEST:
+ return files[0];
+ case HIGHEST:
+ return files[files.length - 1];
+ }
+ }
+ return null;
+ }
+
+ public void setRegistry(Registry registry) {
+ this.registry = registry;
+ }
+
+ public String getLocation() {
+ return root.toString();
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/filter/Filter.java b/bundleplugin/src/main/java/aQute/lib/filter/Filter.java
new file mode 100755
index 0000000..ab75db9
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/filter/Filter.java
@@ -0,0 +1,343 @@
+/**
+ * Copyright (c) 2000 Gatespace AB. All Rights Reserved.
+ *
+ * Gatespace grants Open Services Gateway Initiative (OSGi) an irrevocable,
+ * perpetual, non-exclusive, worldwide, paid-up right and license to
+ * reproduce, display, perform, prepare and have prepared derivative works
+ * based upon and distribute and sublicense this material and derivative
+ * works thereof as set out in the OSGi MEMBER AGREEMENT as of January 24
+ * 2000, for use in accordance with Section 2.2 of the BY-LAWS of the
+ * OSGi MEMBER AGREEMENT.
+ */
+
+package aQute.lib.filter;
+
+import java.lang.reflect.*;
+import java.math.*;
+import java.util.*;
+
+public class Filter {
+ final char WILDCARD = 65535;
+
+ final static int EQ = 0;
+ final static int LE = 1;
+ final static int GE = 2;
+ final static int APPROX = 3;
+
+ private String filter;
+
+ abstract class Query {
+ static final String GARBAGE = "Trailing garbage";
+ static final String MALFORMED = "Malformed query";
+ static final String EMPTY = "Empty list";
+ static final String SUBEXPR = "No subexpression";
+ static final String OPERATOR = "Undefined operator";
+ static final String TRUNCATED = "Truncated expression";
+ static final String EQUALITY = "Only equality supported";
+
+ private String tail;
+
+ boolean match() throws IllegalArgumentException {
+ tail = filter;
+ boolean val = doQuery();
+ if (tail.length() > 0)
+ error(GARBAGE);
+ return val;
+ }
+
+ private boolean doQuery() throws IllegalArgumentException {
+ if (tail.length() < 3 || !prefix("("))
+ error(MALFORMED);
+ boolean val;
+
+ switch (tail.charAt(0)) {
+ case '&':
+ val = doAnd();
+ break;
+ case '|':
+ val = doOr();
+ break;
+ case '!':
+ val = doNot();
+ break;
+ default:
+ val = doSimple();
+ break;
+ }
+
+ if (!prefix(")"))
+ error(MALFORMED);
+ return val;
+ }
+
+ private boolean doAnd() throws IllegalArgumentException {
+ tail = tail.substring(1);
+ boolean val = true;
+ if (!tail.startsWith("("))
+ error(EMPTY);
+ do {
+ if (!doQuery())
+ val = false;
+ } while (tail.startsWith("("));
+ return val;
+ }
+
+ private boolean doOr() throws IllegalArgumentException {
+ tail = tail.substring(1);
+ boolean val = false;
+ if (!tail.startsWith("("))
+ error(EMPTY);
+ do {
+ if (doQuery())
+ val = true;
+ } while (tail.startsWith("("));
+ return val;
+ }
+
+ private boolean doNot() throws IllegalArgumentException {
+ tail = tail.substring(1);
+ if (!tail.startsWith("("))
+ error(SUBEXPR);
+ return !doQuery();
+ }
+
+ private boolean doSimple() throws IllegalArgumentException {
+ int op = 0;
+ Object attr = getAttr();
+
+ if (prefix("="))
+ op = EQ;
+ else if (prefix("<="))
+ op = LE;
+ else if (prefix(">="))
+ op = GE;
+ else if (prefix("~="))
+ op = APPROX;
+ else
+ error(OPERATOR);
+
+ return compare(attr, op, getValue());
+ }
+
+ private boolean prefix(String pre) {
+ if (!tail.startsWith(pre))
+ return false;
+ tail = tail.substring(pre.length());
+ return true;
+ }
+
+ private Object getAttr() {
+ int len = tail.length();
+ int ix = 0;
+ label: for (; ix < len; ix++) {
+ switch (tail.charAt(ix)) {
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '=':
+ case '~':
+ case '*':
+ case '\\':
+ break label;
+ }
+ }
+ String attr = tail.substring(0, ix).toLowerCase();
+ tail = tail.substring(ix);
+ return getProp(attr);
+ }
+
+ abstract Object getProp(String key);
+
+ private String getValue() {
+ StringBuilder sb = new StringBuilder();
+ int len = tail.length();
+ int ix = 0;
+ label: for (; ix < len; ix++) {
+ char c = tail.charAt(ix);
+ switch (c) {
+ case '(':
+ case ')':
+ break label;
+ case '*':
+ sb.append(WILDCARD);
+ break;
+ case '\\':
+ if (ix == len - 1)
+ break label;
+ sb.append(tail.charAt(++ix));
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ tail = tail.substring(ix);
+ return sb.toString();
+ }
+
+ private void error(String m) throws IllegalArgumentException {
+ throw new IllegalArgumentException(m + " " + tail);
+ }
+
+ private boolean compare(Object obj, int op, String s) {
+ if (obj == null)
+ return false;
+ try {
+ Class<?> numClass = obj.getClass();
+ if (numClass == String.class) {
+ return compareString((String) obj, op, s);
+ } else if (numClass == Character.class) {
+ return compareString(obj.toString(), op, s);
+ } else if (numClass == Long.class) {
+ return compareSign(op, Long.valueOf(s).compareTo((Long) obj));
+ } else if (numClass == Integer.class) {
+ return compareSign(op, Integer.valueOf(s).compareTo((Integer) obj));
+ } else if (numClass == Short.class) {
+ return compareSign(op, Short.valueOf(s).compareTo((Short) obj));
+ } else if (numClass == Byte.class) {
+ return compareSign(op, Byte.valueOf(s).compareTo((Byte) obj));
+ } else if (numClass == Double.class) {
+ return compareSign(op, Double.valueOf(s).compareTo((Double) obj));
+ } else if (numClass == Float.class) {
+ return compareSign(op, Float.valueOf(s).compareTo((Float) obj));
+ } else if (numClass == Boolean.class) {
+ if (op != EQ)
+ return false;
+ int a = Boolean.valueOf(s).booleanValue() ? 1 : 0;
+ int b = ((Boolean) obj).booleanValue() ? 1 : 0;
+ return compareSign(op, a - b);
+ } else if (numClass == BigInteger.class) {
+ return compareSign(op, new BigInteger(s).compareTo((BigInteger) obj));
+ } else if (numClass == BigDecimal.class) {
+ return compareSign(op, new BigDecimal(s).compareTo((BigDecimal) obj));
+ } else if (obj instanceof Collection<?>) {
+ for (Object x : (Collection<?>) obj)
+ if (compare(x, op, s))
+ return true;
+ } else if (numClass.isArray()) {
+ int len = Array.getLength(obj);
+ for (int i = 0; i < len; i++)
+ if (compare(Array.get(obj, i), op, s))
+ return true;
+ }
+ } catch (Exception e) {
+ }
+ return false;
+ }
+ }
+
+ class DictQuery extends Query {
+ private Dictionary<?, ?> dict;
+
+ DictQuery(Dictionary<?, ?> dict) {
+ this.dict = dict;
+ }
+
+ Object getProp(String key) {
+ return dict.get(key);
+ }
+ }
+
+ public Filter(String filter) throws IllegalArgumentException {
+ // NYI: Normalize the filter string?
+ this.filter = filter;
+ if (filter == null || filter.length() == 0)
+ throw new IllegalArgumentException("Null query");
+ }
+
+ public boolean match(Dictionary<?, ?> dict) {
+ try {
+ return new DictQuery(dict).match();
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ public String verify() {
+ try {
+ new DictQuery(new Hashtable<Object, Object>()).match();
+ } catch (IllegalArgumentException e) {
+ return e.getMessage();
+ }
+ return null;
+ }
+
+ public String toString() {
+ return filter;
+ }
+
+ public boolean equals(Object obj) {
+ return obj != null && obj instanceof Filter && filter.equals(((Filter) obj).filter);
+ }
+
+ public int hashCode() {
+ return filter.hashCode();
+ }
+
+ boolean compareString(String s1, int op, String s2) {
+ switch (op) {
+ case EQ:
+ return patSubstr(s1, s2);
+ case APPROX:
+ return fixupString(s2).equals(fixupString(s1));
+ default:
+ return compareSign(op, s2.compareTo(s1));
+ }
+ }
+
+ boolean compareSign(int op, int cmp) {
+ switch (op) {
+ case LE:
+ return cmp >= 0;
+ case GE:
+ return cmp <= 0;
+ case EQ:
+ return cmp == 0;
+ default: /* APPROX */
+ return cmp == 0;
+ }
+ }
+
+ String fixupString(String s) {
+ StringBuilder sb = new StringBuilder();
+ int len = s.length();
+ boolean isStart = true;
+ boolean isWhite = false;
+ for (int i = 0; i < len; i++) {
+ char c = s.charAt(i);
+ if (Character.isWhitespace(c)) {
+ isWhite = true;
+ } else {
+ if (!isStart && isWhite)
+ sb.append(' ');
+ if (Character.isUpperCase(c))
+ c = Character.toLowerCase(c);
+ sb.append(c);
+ isStart = false;
+ isWhite = false;
+ }
+ }
+ return sb.toString();
+ }
+
+ boolean patSubstr(String s, String pat) {
+ if (s == null)
+ return false;
+ if (pat.length() == 0)
+ return s.length() == 0;
+ if (pat.charAt(0) == WILDCARD) {
+ pat = pat.substring(1);
+ for (;;) {
+ if (patSubstr(s, pat))
+ return true;
+ if (s.length() == 0)
+ return false;
+ s = s.substring(1);
+ }
+ }
+ if (s.length() == 0 || s.charAt(0) != pat.charAt(0))
+ return false;
+ return patSubstr(s.substring(1), pat.substring(1));
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/filter/packageinfo b/bundleplugin/src/main/java/aQute/lib/filter/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/filter/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/lib/getopt/Arguments.java b/bundleplugin/src/main/java/aQute/lib/getopt/Arguments.java
new file mode 100644
index 0000000..36fc73c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/getopt/Arguments.java
@@ -0,0 +1,8 @@
+package aQute.lib.getopt;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Arguments {
+ String[] arg();
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/getopt/CommandLine.java b/bundleplugin/src/main/java/aQute/lib/getopt/CommandLine.java
new file mode 100644
index 0000000..f95b6c4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/getopt/CommandLine.java
@@ -0,0 +1,472 @@
+package aQute.lib.getopt;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.regex.*;
+
+import aQute.configurable.*;
+import aQute.lib.justif.*;
+import aQute.libg.generics.*;
+import aQute.libg.reporter.*;
+
+/**
+ * Helps parsing command lines. This class takes target object, a primary
+ * command, and a list of arguments. It will then find the command in the target
+ * object. The method of this command must start with a "_" and take an
+ * parameter of Options type. Usually this is an interface that extends Options.
+ * The methods on this interface are options or flags (when they return
+ * boolean).
+ *
+ */
+@SuppressWarnings("unchecked") public class CommandLine {
+ static int LINELENGTH = 60;
+ static Pattern ASSIGNMENT = Pattern.compile("(\\w[\\w\\d]*+)\\s*=\\s*([^\\s]+)\\s*");
+ Reporter reporter;
+ Justif justif = new Justif(60);
+
+ public CommandLine(Reporter reporter) {
+ this.reporter = reporter;
+ }
+
+ /**
+ * Execute a command in a target object with a set of options and arguments
+ * and returns help text if something fails. Errors are reported.
+ */
+
+ public String execute(Object target, String cmd, List<String> input) throws Exception {
+
+ if (cmd.equals("help")) {
+ StringBuilder sb = new StringBuilder();
+ Formatter f = new Formatter(sb);
+ if (input.isEmpty())
+ help(f, target);
+ else {
+ for (String s : input) {
+ help(f, target, s);
+ }
+ }
+ f.flush();
+ justif.wrap(sb);
+ return sb.toString();
+ }
+
+ //
+ // Find the appropriate method
+ //
+
+ List<String> arguments = new ArrayList<String>(input);
+ Map<String, Method> commands = getCommands(target);
+
+ Method m = commands.get(cmd);
+ if (m == null) {
+ reporter.error("No such command %s\n", cmd);
+ return help(target, null, null);
+ }
+
+ //
+ // Parse the options
+ //
+
+ Class<? extends Options> optionClass = (Class<? extends Options>) m.getParameterTypes()[0];
+ Options options = getOptions(optionClass, arguments);
+ if (options == null) {
+ // had some error, already reported
+ return help(target, cmd, null);
+ }
+
+ // Check if we have an @Arguments annotation that
+ // provides patterns for the remainder arguments
+
+ Arguments argumentsAnnotation = optionClass.getAnnotation(Arguments.class);
+ if (argumentsAnnotation != null) {
+ String[] patterns = argumentsAnnotation.arg();
+
+ // Check for commands without any arguments
+
+ if (patterns.length == 0 && arguments.size() > 0) {
+ reporter.error("This command takes no arguments but found %s\n", arguments);
+ return help(target, cmd, null);
+ }
+
+ // Match the patterns to the given command line
+
+ int i = 0;
+ for (; i < patterns.length; i++) {
+ String pattern = patterns[i];
+
+ boolean optional = pattern.matches("\\[.*\\]");
+
+ // Handle vararg
+
+ if (pattern.equals("...")) {
+ i = Integer.MAX_VALUE;
+ break;
+ }
+
+ // Check if we're running out of args
+
+ if (i > arguments.size()) {
+ if (!optional)
+ reporter.error("Missing argument %s\n", patterns[i]);
+ return help(target, cmd, optionClass);
+ }
+ }
+
+ // Check if we have unconsumed arguments left
+
+ if (i < arguments.size()) {
+ reporter.error("Too many arguments specified %s, expecting %s\n", arguments,
+ Arrays.asList(patterns));
+ return help(target, cmd, optionClass);
+ }
+ }
+ if (reporter.getErrors().size() == 0) {
+ m.setAccessible(true);
+ m.invoke(target, options);
+ return null;
+ }
+ return help(target, cmd, optionClass);
+ }
+
+ private String help(Object target, String cmd, Class<? extends Options> type) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ Formatter f = new Formatter(sb);
+ if (cmd == null)
+ help(f, target);
+ else if (type == null)
+ help(f, target, cmd);
+ else
+ help(f, target, cmd, type);
+
+ f.flush();
+ justif.wrap(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Parse the options in a command line and return an interface that provides
+ * the options from this command line. This will parse up to (and including)
+ * -- or an argument that does not start with -
+ *
+ */
+ public <T extends Options> T getOptions(Class<T> specification, List<String> arguments)
+ throws Exception {
+ Map<String, String> properties = Create.map();
+ Map<String, Object> values = new HashMap<String, Object>();
+ Map<String, Method> options = getOptions(specification);
+
+ argloop: while (arguments.size() > 0) {
+
+ String option = arguments.get(0);
+
+ if (option.startsWith("-")) {
+
+ arguments.remove(0);
+
+ if (option.startsWith("--")) {
+
+ if ("--".equals(option))
+ break argloop;
+
+ // Full named option, e.g. --output
+ String name = option.substring(2);
+ Method m = options.get(name);
+ if (m == null)
+ reporter.error("Unrecognized option %s\n", name);
+ else
+ assignOptionValue(values, m, arguments, true);
+
+ } else {
+
+ // Set of single character named options like -a
+
+ charloop: for (int j = 1; j < option.length(); j++) {
+
+ char optionChar = option.charAt(j);
+
+ for (Entry<String, Method> entry : options.entrySet()) {
+ if (entry.getKey().charAt(0) == optionChar) {
+ boolean last = (j + 1) >= option.length();
+ assignOptionValue(values, entry.getValue(),
+ arguments, last);
+ continue charloop;
+ }
+ }
+ reporter.error("No such option -%s\n", optionChar);
+ }
+ }
+ } else {
+ Matcher m = ASSIGNMENT.matcher(option);
+ if (m.matches()) {
+ properties.put(m.group(1), m.group(2));
+ }
+ break;
+ }
+ }
+
+ // check if all required elements are set
+
+ for (Entry<String, Method> entry : options.entrySet()) {
+ Method m = entry.getValue();
+ String name = entry.getKey();
+ if (!values.containsKey(name) && isMandatory(m))
+ reporter.error("Required option --%s not set", name);
+ }
+
+ values.put(".", arguments);
+ values.put(".command", this);
+ values.put(".properties", properties);
+ return Configurable.createConfigurable(specification, values);
+ }
+
+ /**
+ * Answer a list of the options specified in an options interface
+ */
+ private Map<String, Method> getOptions(Class<? extends Options> interf) {
+ Map<String, Method> map = new TreeMap<String, Method>();
+
+ for (Method m : interf.getMethods()) {
+ if (m.getName().startsWith("_"))
+ continue;
+
+ String name;
+
+ Config cfg = m.getAnnotation(Config.class);
+ if (cfg == null || cfg.id() == null || cfg.id().equals(Config.NULL))
+ name = m.getName();
+ else
+ name = cfg.id();
+
+ map.put(name, m);
+ }
+ return map;
+ }
+
+ /**
+ * Assign an option, must handle flags, parameters, and parameters that can
+ * happen multiple times.
+ *
+ * @param options
+ * The command line map
+ * @param args
+ * the args input
+ * @param i
+ * where we are
+ * @param m
+ * the selected method for this option
+ * @param last
+ * if this is the last in a multi single character option
+ * @return
+ */
+ public void assignOptionValue(Map<String, Object> options, Method m, List<String> args,
+ boolean last) {
+ String name = m.getName();
+ Type type = m.getGenericReturnType();
+
+ if (isOption(m)) {
+
+ // The option is a simple flag
+
+ options.put(name, true);
+ } else {
+
+ // The option is followed by an argument
+
+ if (!last) {
+ reporter.error(
+ "Option --%s not last in a set of 1-letter options (%s) but it requires an argument of type ",
+ name, name.charAt(0), getTypeDescriptor(type));
+ return;
+ }
+
+ if (args.isEmpty()) {
+ reporter.error("Missing argument %s for option --%s, -%s ",
+ getTypeDescriptor(type), name, name.charAt(0));
+ return;
+ }
+
+ String parameter = args.remove(0);
+
+ if (Collection.class.isAssignableFrom(m.getReturnType())) {
+
+ Collection<Object> optionValues = (Collection<Object>) options.get(m.getName());
+
+ if (optionValues == null) {
+ optionValues = new ArrayList<Object>();
+ options.put(name, optionValues);
+ }
+
+ optionValues.add(parameter);
+ } else {
+
+ if (options.containsKey(name)) {
+ reporter.error("The option %s can only occur once", name);
+ return;
+ }
+
+ options.put(name, parameter);
+ }
+ }
+ }
+
+ /**
+ * Provide a help text.
+ */
+
+ public void help(Formatter f, Object target, String cmd, Class<? extends Options> specification) {
+ Description descr = specification.getAnnotation(Description.class);
+ Arguments patterns = specification.getAnnotation(Arguments.class);
+ Map<String, Method> options = getOptions(specification);
+
+ String description = descr == null ? "" : descr.value();
+
+ f.format("NAME\n %s - %s\n\n", cmd, description);
+ f.format("SYNOPSIS\n %s [options] ", cmd);
+
+ if (patterns == null)
+ f.format(" ...\n\n");
+ else {
+ String del = " ";
+ for (String pattern : patterns.arg()) {
+ if (pattern.equals("..."))
+ f.format("%s...", del);
+ else
+ f.format("%s<%s>", del, pattern);
+ del = " ";
+ }
+ f.format("\n\n");
+ }
+
+ f.format("OPTIONS\n");
+ for (Entry<String, Method> entry : options.entrySet()) {
+ String optionName = entry.getKey();
+ Method m = entry.getValue();
+
+ Config cfg = m.getAnnotation(Config.class);
+ Description d = m.getAnnotation(Description.class);
+ boolean required = isMandatory(m);
+
+ String methodDescription = cfg != null ? cfg.description() : (d == null ? "" : d
+ .value());
+
+ f.format(" %s -%s, --%s %s%s - %s\n", required ? " " : "[", //
+ optionName.charAt(0), //
+ optionName, //
+ getTypeDescriptor(m.getGenericReturnType()), //
+ required ? " " : "]",//
+ methodDescription);
+ }
+ f.format("\n");
+ }
+
+ static Pattern LAST_PART = Pattern.compile(".*[\\$\\.]([^\\$\\.]+)");
+
+ private static String lastPart(String name) {
+ Matcher m = LAST_PART.matcher(name);
+ if (m.matches())
+ return m.group(1);
+ return name;
+ }
+
+ /**
+ * Show all commands in a target
+ */
+ public void help(Formatter f, Object target) throws Exception {
+ // TODO get help from the class
+ Description descr = target.getClass().getAnnotation(Description.class);
+ if (descr != null) {
+ f.format("%s\n\n", descr.value());
+ }
+ f.format("Available commands: ");
+
+ String del = "";
+ for (String name : getCommands(target).keySet()) {
+ f.format("%s%s", del, name);
+ del = ", ";
+ }
+ f.format("\n");
+
+ }
+
+ /**
+ * Show the full help for a given command
+ */
+ public void help(Formatter f, Object target, String cmd) {
+
+ Method m = getCommands(target).get(cmd);
+ if (m == null)
+ f.format("No such command: %s\n", cmd);
+ else {
+ Class<? extends Options> options = (Class<? extends Options>) m.getParameterTypes()[0];
+ help(f, target, cmd, options);
+ }
+ }
+
+ /**
+ * Parse a class and return a list of command names
+ *
+ * @param target
+ * @return
+ */
+ public Map<String, Method> getCommands(Object target) {
+ Map<String, Method> map = new TreeMap<String, Method>();
+
+ for (Method m : target.getClass().getMethods()) {
+
+ if (m.getParameterTypes().length == 1 && m.getName().startsWith("_")) {
+ Class<?> clazz = m.getParameterTypes()[0];
+ if (Options.class.isAssignableFrom(clazz)) {
+ String name = m.getName().substring(1);
+ map.put(name, m);
+ }
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Answer if the method is marked mandatory
+ */
+ private boolean isMandatory(Method m) {
+ Config cfg = m.getAnnotation(Config.class);
+ if (cfg == null)
+ return false;
+
+ return cfg.required();
+ }
+
+ /**
+ * @param m
+ * @return
+ */
+ private boolean isOption(Method m) {
+ return m.getReturnType() == boolean.class || m.getReturnType() == Boolean.class;
+ }
+
+ /**
+ * Show a type in a nice way
+ */
+
+ private String getTypeDescriptor(Type type) {
+ if (type instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) type;
+ Type c = pt.getRawType();
+ if (c instanceof Class) {
+ if (Collection.class.isAssignableFrom((Class<?>) c)) {
+ return getTypeDescriptor(pt.getActualTypeArguments()[0]) + "*";
+ }
+ }
+ }
+ if (!(type instanceof Class))
+ return "<>";
+
+ Class<?> clazz = (Class<?>) type;
+
+ if (clazz == Boolean.class || clazz == boolean.class)
+ return ""; // Is a flag
+
+ return "<" + lastPart(clazz.getName().toLowerCase()) + ">";
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/getopt/Description.java b/bundleplugin/src/main/java/aQute/lib/getopt/Description.java
new file mode 100644
index 0000000..d5baead
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/getopt/Description.java
@@ -0,0 +1,8 @@
+package aQute.lib.getopt;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Description {
+ String value();
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/getopt/OptionArgument.java b/bundleplugin/src/main/java/aQute/lib/getopt/OptionArgument.java
new file mode 100644
index 0000000..f4e9faa
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/getopt/OptionArgument.java
@@ -0,0 +1,8 @@
+package aQute.lib.getopt;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface OptionArgument {
+ String value();
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/getopt/Options.java b/bundleplugin/src/main/java/aQute/lib/getopt/Options.java
new file mode 100644
index 0000000..aab2b7f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/getopt/Options.java
@@ -0,0 +1,11 @@
+package aQute.lib.getopt;
+
+import java.util.*;
+
+public interface Options {
+ List<String> _();
+ CommandLine _command();
+ Map<String,String> _properties();
+ boolean _ok();
+ boolean _help();
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/getopt/packageinfo b/bundleplugin/src/main/java/aQute/lib/getopt/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/getopt/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
diff --git a/bundleplugin/src/main/java/aQute/lib/hex/Hex.java b/bundleplugin/src/main/java/aQute/lib/hex/Hex.java
new file mode 100755
index 0000000..3c7fa4a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/hex/Hex.java
@@ -0,0 +1,60 @@
+package aQute.lib.hex;
+
+import java.io.*;
+
+
+/*
+ * Hex converter.
+ *
+ * TODO Implement string to byte[]
+ */
+public class Hex {
+ final static char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+ public final static byte[] toByteArray(String string) {
+ string = string.trim();
+ if ( (string.length() & 1) != 0)
+ throw new IllegalArgumentException("a hex string must have an even length");
+
+ byte[]out = new byte[ string.length()/2];
+ for ( int i=0; i < out.length; i++) {
+ int high = nibble(string.charAt(i*2))<<4;
+ int low = nibble(string.charAt(i*2+1));
+ out[i] = (byte) (high + low);
+ }
+ return out;
+ }
+
+
+ public final static int nibble( char c) {
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if ( c>='A' && c<='F')
+ return c - 'A' + 10;
+ if ( c>='a' && c<='f')
+ return c - 'a' + 10;
+
+ throw new IllegalArgumentException("Not a hex digit: " + c);
+ }
+
+ public final static String toHexString(byte data[]) {
+ StringBuilder sb = new StringBuilder();
+ try {
+ append(sb,data);
+ } catch (IOException e) {
+ // cannot happen with sb
+ }
+ return sb.toString();
+ }
+
+ public final static void append( Appendable sb, byte [] data ) throws IOException {
+ for ( int i =0; i<data.length; i++) {
+ sb.append( nibble( data[i] >> 4));
+ sb.append( nibble( data[i]));
+ }
+ }
+
+ private final static char nibble(int i) {
+ return HEX[i & 0xF];
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/hex/packageinfo b/bundleplugin/src/main/java/aQute/lib/hex/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/hex/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/lib/index/Index.java b/bundleplugin/src/main/java/aQute/lib/index/Index.java
new file mode 100644
index 0000000..4414f9d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/index/Index.java
@@ -0,0 +1,349 @@
+package aQute.lib.index;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.*;
+
+/**
+ * <pre>
+ * 0 -> 0, 122 -> 1
+ * 123 -> 123, 244 -> 2
+ * 245 -> 245, ...
+ * </pre>
+ *
+ *
+ */
+public class Index implements Iterable<byte[]> {
+ final static int LEAF = 0;
+ final static int INDEX = 1;
+
+ final static int SIGNATURE = 0;
+ final static int MAGIC = 0x494C4458;
+ final static int KEYSIZE = 4;
+
+ private FileChannel file;
+ final int pageSize = 4096;
+ final int keySize;
+ final int valueSize = 8;
+ final int capacity;
+ public Page root;
+ final LinkedHashMap<Integer, Page> cache = new LinkedHashMap<Integer, Index.Page>();
+ final MappedByteBuffer settings;
+
+ private int nextPage;
+
+ class Page {
+ final static int TYPE_OFFSET = 0;
+ final static int COUNT_OFFSET = 2;
+ final static int START_OFFSET = 4;
+ final int number;
+ boolean leaf;
+ final MappedByteBuffer buffer;
+ int n = 0;
+ boolean dirty;
+
+ Page(int number) throws IOException {
+ this.number = number;
+ buffer = file.map(MapMode.READ_WRITE, ((long) number) * pageSize, pageSize);
+ n = buffer.getShort(COUNT_OFFSET);
+ int type = buffer.getShort(TYPE_OFFSET);
+ leaf = type != 0;
+ }
+
+ Page(int number, boolean leaf) throws IOException {
+ this.number = number;
+ this.leaf = leaf;
+ this.n = 0;
+ buffer = file.map(MapMode.READ_WRITE, ((long) number) * pageSize, pageSize);
+ }
+
+ Iterator<byte[]> iterator() {
+ return new Iterator<byte[]>() {
+ Iterator<byte[]> i;
+ int rover = 0;
+
+ public byte[] next() {
+ if (leaf) {
+ return k(rover++);
+ }
+
+ return i.next();
+ }
+
+ public boolean hasNext() {
+ try {
+ if (leaf)
+ return rover < n;
+ while (i == null || i.hasNext() == false) {
+ int c = (int) c(rover++);
+ i = getPage(c).iterator();
+ }
+ return i.hasNext();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ };
+ }
+
+ void write() throws IOException {
+ buffer.putShort(COUNT_OFFSET, (short) n);
+ buffer.put(TYPE_OFFSET, (byte) (leaf ? 1 : 0));
+ buffer.force();
+ }
+
+ int compare(byte[] key, int i) {
+ int index = pos(i);
+ for (int j = 0; j < keySize; j++, index++) {
+ int a = 0;
+ if (j < key.length)
+ a = key[j] & 0xFF;
+
+ int b = buffer.get(index) & 0xFF;
+ if (a == b)
+ continue;
+
+ return a > b ? 1 : -1;
+ }
+ return 0;
+ }
+
+ int pos(int i) {
+ return START_OFFSET + size(i);
+ }
+
+ int size(int n) {
+ return n * (keySize + valueSize);
+ }
+
+ void copyFrom(Page page, int start, int length) {
+ copy(page.buffer, pos(start), buffer, pos(0), size(length));
+ }
+
+ void copy(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int length) {
+ if (srcPos < dstPos) {
+ while (length-- > 0)
+ dst.put(dstPos + length, src.get(srcPos + length));
+
+ } else {
+ while (length-- > 0)
+ dst.put(dstPos++, src.get(srcPos++));
+ }
+ }
+
+ long search(byte[] k) throws Exception {
+ int cmp = 0;
+ int i = n - 1;
+ while (i >= 0 && (cmp = compare(k, i)) < 0)
+ i--;
+
+ if (leaf) {
+ if (cmp != 0)
+ return -1;
+ return c(i);
+ }
+ long value = c(i);
+ Page child = getPage((int) value);
+ return child.search(k);
+ }
+
+ void insert(byte[] k, long v) throws IOException {
+ if (n == capacity) {
+ int t = capacity / 2;
+ Page left = allocate(leaf);
+ Page right = allocate(leaf);
+ left.copyFrom(this, 0, t);
+ left.n = t;
+ right.copyFrom(this, t, capacity - t);
+ right.n = capacity - t;
+ leaf = false;
+ set(0, left.k(0), left.number);
+ set(1, right.k(0), right.number);
+ n = 2;
+ left.write();
+ right.write();
+ }
+ insertNonFull(k, v);
+ }
+
+ byte[] k(int i) {
+ buffer.position(pos(i));
+ byte[] key = new byte[keySize];
+ buffer.get(key);
+ return key;
+ }
+
+ long c(int i) {
+ if (i < 0) {
+ System.err.println("Arghhh");
+ }
+ int index = pos(i) + keySize;
+ return buffer.getLong(index);
+ }
+
+ void set(int i, byte[] k, long v) {
+ int index = pos(i);
+ for (int j = 0; j < keySize; j++) {
+ byte a = 0;
+ if (j < k.length)
+ a = k[j];
+ buffer.put(index + j, a);
+ }
+ buffer.putLong(index + keySize, v);
+ }
+
+ void insertNonFull(byte[] k, long v) throws IOException {
+ int cmp = 0;
+ int i = n - 1;
+ while (i >= 0 && (cmp = compare(k, i)) < 0)
+ i--;
+
+ if (leaf) {
+ if (cmp != 0) {
+ i++;
+ if (i != n)
+ copy(buffer, pos(i), buffer, pos(i + 1), size(n - i));
+ }
+ set(i, k, v);
+ n++;
+ dirty = true;
+ } else {
+ long value = c(i);
+ Page child = getPage((int) value);
+
+ if (child.n == capacity) {
+ Page left = child;
+ int t = capacity / 2;
+ Page right = allocate(child.leaf);
+ right.copyFrom(left, t, capacity - t);
+ right.n = capacity - t;
+ left.n = t;
+ i++; // place to insert
+ if (i < n) // ok if at end
+ copy(buffer, pos(i), buffer, pos(i + 1), size(n - i));
+ set(i, right.k(0), right.number);
+ n++;
+ assert i < n;
+ child = right.compare(k, 0) >= 0 ? right : left;
+ left.dirty = true;
+ right.dirty = true;
+ this.dirty = true;
+ }
+ child.insertNonFull(k, v);
+ }
+ write();
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ try {
+ toString(sb, "");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return sb.toString();
+ }
+
+ public void toString(StringBuilder sb, String indent) throws IOException {
+ for (int i = 0; i < n; i++) {
+ sb.append(String.format("%s %02d:%02d %20s %s %d\n", indent, number, i,
+ hex(k(i), 0, 4), leaf ? "==" : "->", c(i)));
+ if (!leaf) {
+ long c = c(i);
+ Page sub = getPage((int) c);
+ sub.toString(sb, indent + " ");
+ }
+ }
+ }
+
+ private String hex(byte[] k, int i, int j) {
+ StringBuilder sb = new StringBuilder();
+
+ while (i < j) {
+ int b = 0xFF & k[i];
+ sb.append(nibble(b >> 4));
+ sb.append(nibble(b));
+ i++;
+ }
+ return sb.toString();
+ }
+
+ private char nibble(int i) {
+ i = i & 0xF;
+ return (char) (i >= 10 ? i + 'A' - 10 : i + '0');
+ }
+
+ }
+
+ public Index(File file, int keySize) throws IOException {
+ capacity = (pageSize - Page.START_OFFSET) / (keySize + valueSize);
+ RandomAccessFile raf = new RandomAccessFile(file, "rw");
+ this.file = raf.getChannel();
+ settings = this.file.map(MapMode.READ_WRITE, 0, pageSize);
+ if (this.file.size() == pageSize) {
+ this.keySize = keySize;
+ settings.putInt(SIGNATURE, MAGIC);
+ settings.putInt(KEYSIZE, keySize);
+ nextPage = 1;
+ root = allocate(true);
+ root.n = 1;
+ root.set(0, new byte[KEYSIZE], 0);
+ root.write();
+ } else {
+ if (settings.getInt(SIGNATURE) != MAGIC)
+ throw new IllegalStateException("No Index file, magic is not " + MAGIC);
+
+ this.keySize = settings.getInt(KEYSIZE);
+ if (keySize != 0 && this.keySize != keySize)
+ throw new IllegalStateException("Invalid key size for Index file. The file is "
+ + this.keySize + " and was expected to be " + this.keySize);
+
+ root = getPage(1);
+ nextPage = (int) (this.file.size() / pageSize);
+ }
+ }
+
+ public void insert(byte[] k, long v) throws Exception {
+ root.insert(k, v);
+ }
+
+ public long search(byte[] k) throws Exception {
+ return root.search(k);
+ }
+
+ Page allocate(boolean leaf) throws IOException {
+ Page page = new Page(nextPage++, leaf);
+ cache.put(page.number, page);
+ return page;
+ }
+
+ Page getPage(int number) throws IOException {
+ Page page = cache.get(number);
+ if (page == null) {
+ page = new Page(number);
+ cache.put(number, page);
+ }
+ return page;
+ }
+
+ public String toString() {
+ return root.toString();
+ }
+
+ public void close() throws IOException {
+ file.close();
+ cache.clear();
+ }
+
+ public Iterator<byte[]> iterator() {
+ return root.iterator();
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/index/packageinfo b/bundleplugin/src/main/java/aQute/lib/index/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/index/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/lib/io/IO.java b/bundleplugin/src/main/java/aQute/lib/io/IO.java
new file mode 100644
index 0000000..3d4458e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/io/IO.java
@@ -0,0 +1,388 @@
+package aQute.lib.io;
+
+import java.io.*;
+import java.net.*;
+import java.nio.*;
+import java.security.*;
+import java.util.*;
+
+public class IO {
+
+ public static void copy(Reader r, Writer w) throws IOException {
+ try {
+ char buffer[] = new char[8000];
+ int size = r.read(buffer);
+ while (size > 0) {
+ w.write(buffer, 0, size);
+ size = r.read(buffer);
+ }
+ } finally {
+ r.close();
+ w.flush();
+ }
+ }
+
+ public static void copy(InputStream r, Writer w) throws IOException {
+ copy(r, w, "UTF-8");
+ }
+
+ public static void copy(byte []r, Writer w) throws IOException {
+ copy( new ByteArrayInputStream(r), w, "UTF-8");
+ }
+
+ public static void copy(byte []r, OutputStream w) throws IOException {
+ copy( new ByteArrayInputStream(r), w);
+ }
+
+ public static void copy(InputStream r, Writer w, String charset) throws IOException {
+ try {
+ InputStreamReader isr = new InputStreamReader(r, charset);
+ copy(isr, w);
+ } finally {
+ r.close();
+ }
+ }
+
+ public static void copy(Reader r, OutputStream o) throws IOException {
+ copy(r, o, "UTF-8");
+ }
+
+ public static void copy(Reader r, OutputStream o, String charset) throws IOException {
+ try {
+ OutputStreamWriter osw = new OutputStreamWriter(o, charset);
+ copy(r, osw);
+ } finally {
+ r.close();
+ }
+ }
+
+ public static void copy(InputStream in, OutputStream out) throws IOException {
+ DataOutputStream dos = new DataOutputStream(out);
+ copy(in, (DataOutput) dos);
+ out.flush();
+ }
+
+ public static void copy(InputStream in, DataOutput out) throws IOException {
+ byte[] buffer = new byte[10000];
+ try {
+ int size = in.read(buffer);
+ while (size > 0) {
+ out.write(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ public static void copy(InputStream in, ByteBuffer bb) throws IOException {
+ byte[] buffer = new byte[10000];
+ try {
+ int size = in.read(buffer);
+ while (size > 0) {
+ bb.put(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ public static void copy(URL in, MessageDigest md) throws IOException {
+ copy(in.openStream(), md);
+ }
+
+ public static void copy(File in, MessageDigest md) throws IOException {
+ copy(new FileInputStream(in), md);
+ }
+
+ public static void copy(URLConnection in, MessageDigest md) throws IOException {
+ copy(in.getInputStream(), md);
+ }
+
+
+ public static void copy(InputStream in, MessageDigest md) throws IOException {
+ byte[] buffer = new byte[10000];
+ try {
+ int size = in.read(buffer);
+ while (size > 0) {
+ md.update(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ public static void copy(URL url, File file) throws IOException {
+ URLConnection c = url.openConnection();
+ copy(c, file);
+ }
+
+ public static void copy(URLConnection c, File file) throws IOException {
+ copy(c.getInputStream(), file);
+ }
+
+ public static void copy(InputStream in, URL out) throws IOException {
+ copy(in, out, null);
+ }
+
+ public static void copy(InputStream in, URL out, String method) throws IOException {
+ URLConnection c = out.openConnection();
+ if (c instanceof HttpURLConnection && method != null) {
+ HttpURLConnection http = (HttpURLConnection) c;
+ http.setRequestMethod(method);
+ }
+ c.setDoOutput(true);
+ copy(in, c.getOutputStream());
+ }
+
+ public static void copy(File a, File b) throws IOException {
+ if (a.isFile()) {
+ FileOutputStream out = new FileOutputStream(b);
+ try {
+ copy(new FileInputStream(a), out);
+ } finally {
+ out.close();
+ }
+ } else if (a.isDirectory()) {
+ b.mkdirs();
+ if (!b.isDirectory())
+ throw new IllegalArgumentException(
+ "target directory for a directory must be a directory: " + b);
+ File subs[] = a.listFiles();
+ for (File sub : subs) {
+ copy(sub, new File(b, sub.getName()));
+ }
+ } else
+ throw new FileNotFoundException("During copy: " + a.toString());
+ }
+
+ public static void copy(InputStream a, File b) throws IOException {
+ FileOutputStream out = new FileOutputStream(b);
+ try {
+ copy(a, out);
+ } finally {
+ out.close();
+ }
+ }
+
+ public static void copy(File a, OutputStream b) throws IOException {
+ copy(new FileInputStream(a), b);
+ }
+
+ public static String collect(File a, String encoding) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ copy(a, out);
+ return new String(out.toByteArray(), encoding);
+ }
+
+ public static String collect(URL a, String encoding) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ copy(a.openStream(), out);
+ return new String(out.toByteArray(), encoding);
+ }
+
+ public static String collect(URL a) throws IOException {
+ return collect(a, "UTF-8");
+ }
+
+ public static String collect(File a) throws IOException {
+ return collect(a, "UTF-8");
+ }
+
+ public static String collect(String a) throws IOException {
+ return collect(new File(a), "UTF-8");
+ }
+
+ public static String collect(InputStream a, String encoding) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ copy(a, out);
+ return new String(out.toByteArray(), encoding);
+ }
+
+ public static String collect(InputStream a) throws IOException {
+ return collect(a, "UTF-8");
+ }
+
+ public static String collect(Reader a) throws IOException {
+ StringWriter sw = new StringWriter();
+ char[] buffer = new char[10000];
+ int size = a.read(buffer);
+ while (size > 0) {
+ sw.write(buffer, 0, size);
+ size = a.read(buffer);
+ }
+ return sw.toString();
+ }
+
+ public static File getFile(File base, String file) {
+ File f = new File(file);
+ if (f.isAbsolute())
+ return f;
+ int n;
+
+ f = base.getAbsoluteFile();
+ while ((n = file.indexOf('/')) > 0) {
+ String first = file.substring(0, n);
+ file = file.substring(n + 1);
+ if (first.equals(".."))
+ f = f.getParentFile();
+ else
+ f = new File(f, first);
+ }
+ if (file.equals(".."))
+ return f.getParentFile();
+ return new File(f, file).getAbsoluteFile();
+ }
+
+ public static void delete(File f) {
+ f = f.getAbsoluteFile();
+ if (f.getParentFile() == null)
+ throw new IllegalArgumentException("Cannot recursively delete root for safety reasons");
+
+ if (f.isDirectory()) {
+ File[] subs = f.listFiles();
+ for (File sub : subs)
+ delete(sub);
+ }
+
+ f.delete();
+ }
+
+ public static long drain(InputStream in) throws IOException {
+ long result = 0;
+ byte[] buffer = new byte[10000];
+ try {
+ int size = in.read(buffer);
+ while (size >= 0) {
+ result += size;
+ size = in.read(buffer);
+ }
+ } finally {
+ in.close();
+ }
+ return result;
+ }
+
+ public void copy(Collection<?> c, OutputStream out) throws IOException {
+ Writer w = new OutputStreamWriter(out,"UTF-8");
+ PrintWriter ps = new PrintWriter(w);
+ for (Object o : c) {
+ ps.println(o);
+ }
+ ps.flush();
+ w.flush();
+ }
+
+ public static Throwable close(Closeable in) {
+ if ( in == null)
+ return null;
+
+ try {
+ in.close();
+ return null;
+ } catch (Throwable e) {
+ return e;
+ }
+ }
+
+ public static URL toURL(String s, File base) throws MalformedURLException {
+ int n = s.indexOf(':');
+ if (n > 0 && n < 10) {
+ // is url
+ return new URL(s);
+ }
+ return getFile(base, s).toURI().toURL();
+ }
+
+ public static void store(Object o, File out) throws IOException {
+ store(o, out, "UTF-8");
+ }
+
+ public static void store(Object o, File out, String encoding) throws IOException {
+ FileOutputStream fout = new FileOutputStream(out);
+ try {
+ store(o, fout, encoding);
+ } finally {
+ fout.close();
+ }
+ }
+
+ public static void store(Object o, OutputStream fout) throws UnsupportedEncodingException,
+ IOException {
+ store(o, fout, "UTF-8");
+ }
+
+ public static void store(Object o, OutputStream fout, String encoding)
+ throws UnsupportedEncodingException, IOException {
+ String s;
+
+ if (o == null)
+ s = "";
+ else
+ s = o.toString();
+
+ try {
+ fout.write(s.getBytes(encoding));
+ } finally {
+ fout.close();
+ }
+ }
+
+ public static InputStream stream(String s) {
+ try {
+ return new ByteArrayInputStream(s.getBytes("UTF-8"));
+ } catch (Exception e) {
+ // Ignore
+ return null;
+ }
+ }
+
+ public static InputStream stream(String s, String encoding) throws UnsupportedEncodingException {
+ return new ByteArrayInputStream(s.getBytes(encoding));
+ }
+
+ public static InputStream stream(File s) throws FileNotFoundException {
+ return new FileInputStream(s);
+ }
+
+ public static InputStream stream(URL s) throws IOException {
+ return s.openStream();
+ }
+
+ public static Reader reader(String s) {
+ return new StringReader(s);
+ }
+
+
+ public static BufferedReader reader(File f, String encoding) throws IOException {
+ return reader( new FileInputStream(f), encoding);
+ }
+
+ public static BufferedReader reader(File f) throws IOException {
+ return reader(f,"UTF-8");
+ }
+
+ public static PrintWriter writer(File f, String encoding) throws IOException {
+ return writer( new FileOutputStream(f),encoding);
+ }
+
+ public static PrintWriter writer(File f) throws IOException {
+ return writer(f, "UTF-8");
+ }
+
+ public static PrintWriter writer(OutputStream out, String encoding) throws IOException {
+ return new PrintWriter( new OutputStreamWriter( out,encoding));
+ }
+ public static BufferedReader reader(InputStream in, String encoding) throws IOException {
+ return new BufferedReader( new InputStreamReader(in, encoding));
+ }
+
+ public static BufferedReader reader(InputStream in) throws IOException {
+ return reader(in, "UTF-8");
+ }
+ public static PrintWriter writer(OutputStream out) throws IOException {
+ return writer(out, "UTF-8");
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/io/LimitedInputStream.java b/bundleplugin/src/main/java/aQute/lib/io/LimitedInputStream.java
new file mode 100644
index 0000000..bf9a21f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/io/LimitedInputStream.java
@@ -0,0 +1,78 @@
+package aQute.lib.io;
+
+import java.io.*;
+
+public class LimitedInputStream extends InputStream {
+
+ final InputStream in;
+ final int size;
+ int left;
+
+ public LimitedInputStream(InputStream in, int size) {
+ this.in = in;
+ this.left = size;
+ this.size = size;
+ }
+
+ @Override public int read() throws IOException {
+ if (left <= 0) {
+ eof();
+ return -1;
+ }
+
+ left--;
+ return in.read();
+ }
+
+ @Override public int available() throws IOException {
+ return Math.min(left, in.available());
+ }
+
+ @Override public void close() throws IOException {
+ eof();
+ in.close();
+ }
+
+ protected void eof() {
+ }
+
+ @Override public synchronized void mark(int readlimit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public boolean markSupported() {
+ return false;
+ }
+
+ @Override public int read(byte[] b, int off, int len) throws IOException {
+ int min = Math.min(len, left);
+ if (min == 0)
+ return 0;
+
+ int read = in.read(b, off, min);
+ if (read > 0)
+ left -= read;
+ return read;
+ }
+
+ @Override public int read(byte[] b) throws IOException {
+ return read(b,0,b.length);
+ }
+
+ @Override public synchronized void reset() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public long skip(long n) throws IOException {
+ long count = 0;
+ byte buffer[] = new byte[1024];
+ while ( n > 0 && read() >= 0) {
+ int size = read(buffer);
+ if ( size <= 0)
+ return count;
+ count+=size;
+ n-=size;
+ }
+ return count;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/io/packageinfo b/bundleplugin/src/main/java/aQute/lib/io/packageinfo
new file mode 100644
index 0000000..897578f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/io/packageinfo
@@ -0,0 +1 @@
+version 1.2.0
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ArrayHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ArrayHandler.java
new file mode 100644
index 0000000..62af4a7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/ArrayHandler.java
@@ -0,0 +1,40 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class ArrayHandler extends Handler {
+ Type componentType;
+
+ ArrayHandler(Class< ? > rawClass, Type componentType) {
+ this.componentType = componentType;
+ }
+
+ @Override
+ void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ app.append("[");
+ String del = "";
+ int l = Array.getLength(object);
+ for (int i = 0; i < l; i++) {
+ app.append(del);
+ app.encode(Array.get(object, i), componentType, visited);
+ del = ",";
+ }
+ app.append("]");
+ }
+
+ @Override
+ Object decodeArray(Decoder r) throws Exception {
+ ArrayList<Object> list = new ArrayList<Object>();
+ r.codec.parseArray(list, componentType, r);
+ Object array = Array.newInstance(r.codec.getRawClass(componentType),
+ list.size());
+ int n = 0;
+ for (Object o : list)
+ Array.set(array, n++, o);
+
+ return array;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/BooleanHandler.java b/bundleplugin/src/main/java/aQute/lib/json/BooleanHandler.java
new file mode 100644
index 0000000..d3b56ea
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/BooleanHandler.java
@@ -0,0 +1,30 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class BooleanHandler extends Handler {
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ app.append( object.toString());
+ }
+
+ @Override Object decode(boolean s) {
+ return s;
+ }
+
+ @Override Object decode(String s) {
+ return Boolean.parseBoolean(s);
+ }
+
+ @Override Object decode(Number s) {
+ return s.intValue() != 0;
+ }
+
+ @Override Object decode() {
+ return false;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
new file mode 100644
index 0000000..65f5f74
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
@@ -0,0 +1,30 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.base64.*;
+
+public class ByteArrayHandler extends Handler {
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ StringHandler.string(app, Base64.encodeBase64((byte[]) object));
+ }
+
+ @Override Object decodeArray(Decoder r) throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ ArrayList<Object> list = new ArrayList<Object>();
+ r.codec.parseArray(list, Byte.class, r);
+ for (Object b : list) {
+ out.write(((Byte) b).byteValue());
+ }
+ return out.toByteArray();
+ }
+
+ @Override Object decode(String s) throws Exception {
+ return Base64.decodeBase64(s);
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/CharacterHandler.java b/bundleplugin/src/main/java/aQute/lib/json/CharacterHandler.java
new file mode 100644
index 0000000..7009e0c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/CharacterHandler.java
@@ -0,0 +1,31 @@
+package aQute.lib.json;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+public class CharacterHandler extends Handler {
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws Exception {
+ Character c = (Character) object;
+ int v = (int) c.charValue();
+ app.append( v+"" );
+ }
+
+ @Override Object decode(boolean s) {
+ return s ? 't' : 'f';
+ }
+
+ @Override Object decode(String s) {
+ return (char) Integer.parseInt(s);
+ }
+
+ @Override Object decode(Number s) {
+ return (char) s.shortValue();
+ }
+
+ @Override Object decode() {
+ return 0;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java b/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java
new file mode 100644
index 0000000..3fb14ee
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java
@@ -0,0 +1,57 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+public class CollectionHandler extends Handler {
+ Class<?> rawClass;
+ Type componentType;
+
+ CollectionHandler(Class<?> rawClass, Type componentType) {
+ this.componentType = componentType;
+ if (rawClass.isInterface()) {
+ if (rawClass.isAssignableFrom(ArrayList.class))
+ rawClass = ArrayList.class;
+ else if (rawClass.isAssignableFrom(LinkedList.class))
+ rawClass = LinkedList.class;
+ else if (rawClass.isAssignableFrom(HashSet.class))
+ rawClass = HashSet.class;
+ else if (rawClass.isAssignableFrom(TreeSet.class))
+ rawClass = TreeSet.class;
+ else if (rawClass.isAssignableFrom(Vector.class))
+ rawClass = Vector.class;
+ else if (rawClass.isAssignableFrom(ConcurrentLinkedQueue.class))
+ rawClass = ConcurrentLinkedQueue.class;
+ else if (rawClass.isAssignableFrom(CopyOnWriteArrayList.class))
+ rawClass = CopyOnWriteArrayList.class;
+ else if (rawClass.isAssignableFrom(CopyOnWriteArraySet.class))
+ rawClass = CopyOnWriteArraySet.class;
+ else
+ throw new IllegalArgumentException("Unknown interface type for collection: "
+ + rawClass);
+ }
+ this.rawClass = rawClass;
+ }
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ Collection<?> collection = (Collection<?>) object;
+
+ app.append("[");
+ String del = "";
+ for (Object o : collection) {
+ app.append(del);
+ app.encode(o, componentType, visited);
+ del = ",";
+ }
+ app.append("]");
+ }
+
+ @SuppressWarnings("unchecked") @Override Object decodeArray(Decoder r) throws Exception {
+ Collection<Object> c = (Collection<Object>) rawClass.newInstance();
+ r.codec.parseArray(c, componentType, r);
+ return c;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/DateHandler.java b/bundleplugin/src/main/java/aQute/lib/json/DateHandler.java
new file mode 100644
index 0000000..d4f262e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/DateHandler.java
@@ -0,0 +1,30 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+
+public class DateHandler extends Handler {
+ final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ String s;
+ synchronized (sdf) {
+ s = sdf.format((Date) object);
+ }
+ StringHandler.string(app, s);
+ }
+
+ @Override Object decode(String s) throws Exception {
+ synchronized (sdf) {
+ return sdf.parse(s);
+ }
+ }
+
+ @Override Object decode(Number s) throws Exception {
+ return new Date(s.longValue());
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/Decoder.java b/bundleplugin/src/main/java/aQute/lib/json/Decoder.java
new file mode 100644
index 0000000..3cbed4b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/Decoder.java
@@ -0,0 +1,137 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.security.*;
+import java.util.*;
+
+public class Decoder implements Closeable {
+ final JSONCodec codec;
+ Reader reader;
+ int current;
+ MessageDigest digest;
+ Map<String, Object> extra;
+ String encoding = "UTF-8";
+
+ boolean strict;
+
+ Decoder(JSONCodec codec) {
+ this.codec = codec;
+ }
+
+ public Decoder from(File file) throws Exception {
+ return from(new FileInputStream(file));
+ }
+
+ public Decoder from(InputStream in) throws Exception {
+ return from(new InputStreamReader(in, encoding));
+ }
+
+ public Decoder charset(String encoding) {
+ this.encoding = encoding;
+ return this;
+ }
+
+ public Decoder strict() {
+ this.strict = true;
+ return this;
+ }
+
+ public Decoder from(Reader in) throws Exception {
+ reader = in;
+ read();
+ return this;
+ }
+
+ public Decoder faq(String in) throws Exception {
+ return from(in.replace('\'', '"'));
+ }
+
+ public Decoder from(String in) throws Exception {
+ return from(new StringReader(in));
+ }
+
+ public Decoder mark() throws NoSuchAlgorithmException {
+ if (digest == null)
+ digest = MessageDigest.getInstance("SHA1");
+ digest.reset();
+ return this;
+ }
+
+ public byte[] digest() {
+ if (digest == null)
+ return null;
+
+ return digest.digest();
+ }
+
+ @SuppressWarnings("unchecked") public <T> T get(Class<T> clazz) throws Exception {
+ return (T) codec.decode(clazz, this);
+ }
+
+ public Object get(Type type) throws Exception {
+ return codec.decode(type, this);
+ }
+
+ public Object get() throws Exception {
+ return codec.decode(null, this);
+ }
+
+ int read() throws Exception {
+ current = reader.read();
+ if (digest != null) {
+ digest.update((byte) (current / 256));
+ digest.update((byte) (current % 256));
+ }
+ return current;
+ }
+
+ int current() {
+ return current;
+ }
+
+ /**
+ * Skip any whitespace.
+ *
+ * @return
+ * @throws Exception
+ */
+ int skipWs() throws Exception {
+ while (Character.isWhitespace(current()))
+ read();
+ return current();
+ }
+
+ /**
+ * Skip any whitespace.
+ *
+ * @return
+ * @throws Exception
+ */
+ int next() throws Exception {
+ read();
+ return skipWs();
+ }
+
+ void expect(String s) throws Exception {
+ for (int i = 0; i < s.length(); i++)
+ if (!(s.charAt(i) == read()))
+ throw new IllegalArgumentException("Expected " + s + " but got something different");
+ read();
+ }
+
+ public boolean isEof() throws Exception {
+ int c = skipWs();
+ return c < 0;
+ }
+
+ public void close() throws IOException {
+ reader.close();
+ }
+
+ public Map<String, Object> getExtra() {
+ if (extra == null)
+ extra = new HashMap<String, Object>();
+ return extra;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/Encoder.java b/bundleplugin/src/main/java/aQute/lib/json/Encoder.java
new file mode 100644
index 0000000..09990ed
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/Encoder.java
@@ -0,0 +1,112 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.security.*;
+import java.util.*;
+
+public class Encoder implements Appendable, Closeable, Flushable {
+ final JSONCodec codec;
+ Appendable app;
+ MessageDigest digest;
+ boolean writeDefaults;
+ String encoding = "UTF-8";
+
+ Encoder(JSONCodec codec) {
+ this.codec = codec;
+ }
+
+ public Encoder put(Object object) throws Exception {
+ if (app == null)
+ to();
+
+ codec.encode(this, object, null, new IdentityHashMap<Object, Type>());
+ return this;
+ }
+
+ public Encoder mark() throws NoSuchAlgorithmException {
+ if (digest == null)
+ digest = MessageDigest.getInstance("SHA1");
+ digest.reset();
+ return this;
+ }
+
+ public byte[] digest() throws NoSuchAlgorithmException, IOException {
+ if (digest == null)
+ return null;
+ append('\n');
+ return digest.digest();
+ }
+
+ public Encoder to() throws IOException {
+ to(new StringWriter());
+ return this;
+ }
+
+ public Encoder to(File file) throws IOException {
+ return to(new FileOutputStream(file));
+ }
+
+ public Encoder charset(String encoding) {
+ this.encoding = encoding;
+ return this;
+ }
+
+ public Encoder to(OutputStream out) throws IOException {
+ return to(new OutputStreamWriter(out, encoding));
+ }
+
+ public Encoder to(Appendable out) throws IOException {
+ app = out;
+ return this;
+ }
+
+ public Appendable append(char c) throws IOException {
+ if (digest != null) {
+ digest.update((byte) (c / 256));
+ digest.update((byte) (c % 256));
+ }
+ app.append(c);
+ return this;
+ }
+
+ public Appendable append(CharSequence sq) throws IOException {
+ return append(sq, 0, sq.length());
+ }
+
+ public Appendable append(CharSequence sq, int start, int length) throws IOException {
+ if (digest != null) {
+ for (int i = start; i < length; i++) {
+ char c = sq.charAt(i);
+ digest.update((byte) (c / 256));
+ digest.update((byte) (c % 256));
+ }
+ }
+ app.append(sq, start, length);
+ return this;
+ }
+
+ public String toString() {
+ return app.toString();
+ }
+
+ public void close() throws IOException {
+ if (app != null && app instanceof Closeable)
+ ((Closeable) app).close();
+ }
+
+ void encode(Object object, Type type, Map<Object, Type> visited) throws Exception {
+ codec.encode(this, object, type, visited);
+ }
+
+ public Encoder writeDefaults() {
+ writeDefaults = true;
+ return this;
+ }
+
+ public void flush() throws IOException {
+ if (app instanceof Flushable) {
+ ((Flushable) app).flush();
+ }
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/EnumHandler.java b/bundleplugin/src/main/java/aQute/lib/json/EnumHandler.java
new file mode 100644
index 0000000..dbdb492
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/EnumHandler.java
@@ -0,0 +1,24 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class EnumHandler extends Handler {
+ @SuppressWarnings("rawtypes")
+ final Class type;
+
+ public EnumHandler(Class<?> type) {
+ this.type = type;
+ }
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ StringHandler.string(app, object.toString());
+ }
+
+ @SuppressWarnings("unchecked") Object decode(String s) throws Exception {
+ return Enum.valueOf(type, s);
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/FileHandler.java b/bundleplugin/src/main/java/aQute/lib/json/FileHandler.java
new file mode 100644
index 0000000..2f0eea5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/FileHandler.java
@@ -0,0 +1,38 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.base64.*;
+
+public class FileHandler extends Handler {
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ File f = (File) object;
+ if ( !f.isFile())
+ throw new RuntimeException("Encoding a file requires the file to exist and to be a normal file " + f );
+
+ FileInputStream in = new FileInputStream(f);
+ try {
+ app.append('"');
+ Base64.encode(in, app);
+ app.append('"');
+ } finally {
+ in.close();
+ }
+ }
+
+ Object decode(String s) throws Exception {
+ File tmp = File.createTempFile("json", ".bin");
+ FileOutputStream fout = new FileOutputStream(tmp);
+ try {
+ Base64.decode(new StringReader(s), fout);
+ } finally {
+ fout.close();
+ }
+ return tmp;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/Handler.java b/bundleplugin/src/main/java/aQute/lib/json/Handler.java
new file mode 100644
index 0000000..18957e8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/Handler.java
@@ -0,0 +1,35 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+abstract class Handler {
+ abstract void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception;
+
+ Object decodeObject(Decoder isr) throws Exception {
+ throw new UnsupportedOperationException("Cannot be mapped to object " + this);
+ }
+
+ Object decodeArray(Decoder isr) throws Exception {
+ throw new UnsupportedOperationException("Cannot be mapped to array " + this);
+ }
+
+ Object decode(String s) throws Exception {
+ throw new UnsupportedOperationException("Cannot be mapped to string " + this);
+ }
+
+ Object decode(Number s) throws Exception {
+ throw new UnsupportedOperationException("Cannot be mapped to number " + this);
+ }
+
+ Object decode(boolean s) {
+ throw new UnsupportedOperationException("Cannot be mapped to boolean " + this);
+ }
+
+ Object decode() {
+ return null;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
new file mode 100644
index 0000000..a45b5b4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
@@ -0,0 +1,481 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+/**
+ * This is a simple JSON Coder and Encoder that uses the Java type system to
+ * convert data objects to JSON and JSON to (type safe) Java objects. The
+ * conversion is very much driven by classes and their public fields. Generic
+ * information, when present is taken into account. </p> Usage patterns to
+ * encode:
+ *
+ * <pre>
+ * JSONCoder codec = new JSONCodec(); //
+ * assert "1".equals( codec.enc().to().put(1).toString());
+ * assert "[1,2,3]".equals( codec.enc().to().put(Arrays.asList(1,2,3).toString());
+ *
+ * Map m = new HashMap();
+ * m.put("a", "A");
+ * assert "{\"a\":\"A\"}".equals( codec.enc().to().put(m).toString());
+ *
+ * static class D { public int a; }
+ * D d = new D();
+ * d.a = 41;
+ * assert "{\"a\":41}".equals( codec.enc().to().put(d).toString());
+ * </pre>
+ *
+ * It is possible to redirect the encoder to another output (default is a
+ * string). See {@link Encoder#to()},{@link Encoder#to(File))},
+ * {@link Encoder#to(OutputStream)}, {@link Encoder#to(Appendable))}. To reset
+ * the string output call {@link Encoder#to()}.
+ * <p/>
+ * This Codec class can be used in a concurrent environment. The Decoders and
+ * Encoders, however, must only be used in a single thread.
+ */
+public class JSONCodec {
+ final static String START_CHARACTERS = "[{\"-0123456789tfn";
+
+ // Handlers
+ private final static WeakHashMap<Type, Handler> handlers = new WeakHashMap<Type, Handler>();
+ private static StringHandler sh = new StringHandler();
+ private static BooleanHandler bh = new BooleanHandler();
+ private static CharacterHandler ch = new CharacterHandler();
+ private static CollectionHandler dch = new CollectionHandler(
+ ArrayList.class,
+ Object.class);
+ private static SpecialHandler sph = new SpecialHandler(
+ Pattern.class,
+ null, null);
+ private static DateHandler sdh = new DateHandler();
+ private static FileHandler fh = new FileHandler();
+ private static ByteArrayHandler byteh = new ByteArrayHandler();
+
+ /**
+ * Create a new Encoder with the state and appropriate API.
+ *
+ * @return an Encoder
+ */
+ public Encoder enc() {
+ return new Encoder(this);
+ }
+
+ /**
+ * Create a new Decoder with the state and appropriate API.
+ *
+ * @return a Decoder
+ */
+ public Decoder dec() {
+ return new Decoder(this);
+ }
+
+ /*
+ * Work horse encode methods, all encoding ends up here.
+ */
+ void encode(Encoder app, Object object, Type type, Map<Object, Type> visited) throws Exception {
+
+ // Get the null out of the way
+
+ if (object == null) {
+ app.append("null");
+ return;
+ }
+
+ // If we have no type or the type is Object.class
+ // we take the type of the object itself. Normally types
+ // come from declaration sites (returns, fields, methods, etc)
+ // and contain generic info.
+
+ if (type == null || type == Object.class)
+ type = object.getClass();
+
+ // Dispatch to the handler who knows how to handle the given type.
+ Handler h = getHandler(type);
+ h.encode(app, object, visited);
+ }
+
+ /*
+ * This method figures out which handler should handle the type specific
+ * stuff. It returns a handler for each type. If no appropriate handler
+ * exists, it will create one for the given type. There are actually quite a
+ * lot of handlers since Java is not very object oriented.
+ *
+ * @param type
+ *
+ * @return
+ *
+ * @throws Exception
+ */
+ Handler getHandler(Type type) throws Exception {
+
+ // First the static hard coded handlers for the common types.
+
+ if (type == String.class)
+ return sh;
+
+ if (type == Boolean.class || type == boolean.class)
+ return bh;
+
+ if (type == byte[].class)
+ return byteh;
+
+ if (Character.class == type || char.class == type)
+ return ch;
+
+ if (Pattern.class == type)
+ return sph;
+
+ if (Date.class == type)
+ return sdh;
+
+ if (File.class == type)
+ return fh;
+
+ Handler h;
+ synchronized (handlers) {
+ h = handlers.get(type);
+ }
+
+ if (h != null)
+ return h;
+
+ if (type instanceof Class) {
+
+ Class<?> clazz = (Class<?>) type;
+
+ if (Enum.class.isAssignableFrom(clazz))
+ h = new EnumHandler(clazz);
+ else if (Collection.class.isAssignableFrom(clazz)) // A Non Generic
+ // collection
+
+ h = dch;
+ else if (clazz.isArray()) // Non generic array
+ h = new ArrayHandler(clazz, clazz.getComponentType());
+ else if (Map.class.isAssignableFrom(clazz)) // A Non Generic map
+ h = new MapHandler(clazz, Object.class, Object.class);
+ else if (Number.class.isAssignableFrom(clazz) || clazz.isPrimitive())
+ h = new NumberHandler(clazz);
+ else {
+ Method valueOf = null;
+ Constructor<?> constructor = null;
+
+ try {
+ constructor = clazz.getConstructor(String.class);
+ } catch (Exception e) {
+ // Ignore
+ }
+ try {
+ valueOf = clazz.getMethod("valueOf", String.class);
+ } catch (Exception e) {
+ // Ignore
+ }
+ if (constructor != null || valueOf != null)
+ h = new SpecialHandler(clazz, constructor, valueOf);
+ else
+ h = new ObjectHandler(this, clazz); // Hmm, might not be a
+ // data class ...
+ }
+
+ } else {
+
+ // We have generic information available
+ // We only support generics on Collection, Map, and arrays
+
+ if (type instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) type;
+ Type rawType = pt.getRawType();
+ if (rawType instanceof Class) {
+ Class<?> rawClass = (Class<?>) rawType;
+ if (Collection.class.isAssignableFrom(rawClass))
+ h = new CollectionHandler(rawClass, pt.getActualTypeArguments()[0]);
+ else if (Map.class.isAssignableFrom(rawClass))
+ h = new MapHandler(rawClass, pt.getActualTypeArguments()[0],
+ pt.getActualTypeArguments()[1]);
+ else
+ throw new IllegalArgumentException(
+ "Found a parameterized type that is not a map or collection");
+ }
+ } else if (type instanceof GenericArrayType) {
+ GenericArrayType gat = (GenericArrayType) type;
+ h = new ArrayHandler(getRawClass(type), gat.getGenericComponentType());
+ } else
+ throw new IllegalArgumentException(
+ "Found a parameterized type that is not a map or collection");
+ }
+ synchronized (handlers) {
+ // We might actually have duplicates
+ // but who cares? They should be identical
+ handlers.put(type, h);
+ }
+ return h;
+ }
+
+ Object decode(Type type, Decoder isr) throws Exception {
+ int c = isr.skipWs();
+ Handler h;
+
+ if (type == null || type == Object.class) {
+
+ // Establish default behavior when we run without
+ // type information
+
+ switch (c) {
+ case '{':
+ type = LinkedHashMap.class;
+ break;
+
+ case '[':
+ type = ArrayList.class;
+ break;
+
+ case '"':
+ return parseString(isr);
+
+ case 'n':
+ isr.expect("ull");
+ return null;
+
+ case 't':
+ isr.expect("rue");
+ return true;
+
+ case 'f':
+ isr.expect("alse");
+ return false;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ return parseNumber(isr);
+
+ default:
+ throw new IllegalArgumentException("Invalid character at begin of token: "
+ + (char) c);
+ }
+ }
+
+ h = getHandler(type);
+
+ switch (c) {
+ case '{':
+ return h.decodeObject(isr);
+
+ case '[':
+ return h.decodeArray(isr);
+
+ case '"':
+ return h.decode(parseString(isr));
+
+ case 'n':
+ isr.expect("ull");
+ return h.decode();
+
+ case 't':
+ isr.expect("rue");
+ return h.decode(Boolean.TRUE);
+
+ case 'f':
+ isr.expect("alse");
+ return h.decode(Boolean.FALSE);
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ return h.decode(parseNumber(isr));
+
+ default:
+ throw new IllegalArgumentException("Unexpected character in input stream: " + (char) c);
+ }
+ }
+
+ String parseString(Decoder r) throws Exception {
+ assert r.current() == '"';
+
+ int c = r.next(); // skip first "
+
+ StringBuilder sb = new StringBuilder();
+ while (c != '"') {
+ if (c < 0 || Character.isISOControl(c))
+ throw new IllegalArgumentException(
+ "JSON strings may not contain control characters: " + r.current());
+
+ if (c == '\\') {
+ c = r.read();
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ sb.append((char)c);
+ break;
+
+ case 'b':
+ sb.append('\b');
+ break;
+
+ case 'f':
+ sb.append('\f');
+ break;
+ case 'n':
+ sb.append('\n');
+ break;
+ case 'r':
+ sb.append('\r');
+ break;
+ case 't':
+ sb.append('\t');
+ break;
+ case 'u':
+ int a3 = hexDigit(r.read()) << 12;
+ int a2 = hexDigit(r.read()) << 8;
+ int a1 = hexDigit(r.read()) << 4;
+ int a0 = hexDigit(r.read()) << 0;
+ c = a3 + a2 + a1 + a0;
+ sb.append((char) c);
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ "The only characters after a backslash are \", \\, b, f, n, r, t, and u but got "
+ + c);
+ }
+ } else
+ sb.append((char) c);
+
+ c = r.read();
+ }
+ assert c == '"';
+ r.read(); // skip quote
+ return sb.toString();
+ }
+
+ private int hexDigit(int c) throws EOFException {
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ throw new IllegalArgumentException("Invalid hex character: " + c);
+ }
+
+ private Number parseNumber(Decoder r) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ boolean d = false;
+
+ if (r.current() == '-') {
+ sb.append('-');
+ r.read();
+ }
+
+ int c = r.current();
+ if (c == '0') {
+ sb.append('0');
+ c = r.read();
+ } else if (c >= '1' && c <= '9') {
+ sb.append((char) c);
+ c = r.read();
+
+ while (c >= '0' && c <= '9') {
+ sb.append((char) c);
+ c = r.read();
+ }
+ } else
+ throw new IllegalArgumentException("Expected digit");
+
+ if (c == '.') {
+ d = true;
+ sb.append('.');
+ c = r.read();
+ while (c >= '0' && c <= '9') {
+ sb.append((char) c);
+ c = r.read();
+ }
+ }
+ if (c == 'e' || c == 'E') {
+ d = true;
+ sb.append('e');
+ c = r.read();
+ if (c == '+') {
+ sb.append('+');
+ c = r.read();
+ } else if (c == '-') {
+ sb.append('-');
+ c = r.read();
+ }
+ while (c >= '0' && c <= '9') {
+ sb.append((char) c);
+ c = r.read();
+ }
+ }
+ if (d)
+ return Double.parseDouble(sb.toString());
+ long l = Long.parseLong(sb.toString());
+ if (l > Integer.MAX_VALUE || l < Integer.MIN_VALUE)
+ return l;
+ return (int) l;
+ }
+
+ void parseArray(Collection<Object> list, Type componentType, Decoder r) throws Exception {
+ assert r.current() == '[';
+ int c = r.next();
+ while (START_CHARACTERS.indexOf(c) >= 0) {
+ Object o = decode(componentType, r);
+ list.add(o);
+
+ c = r.skipWs();
+ if (c == ']')
+ break;
+
+ if (c == ',') {
+ c = r.next();
+ continue;
+ }
+
+ throw new IllegalArgumentException(
+ "Invalid character in parsing list, expected ] or , but found " + (char) c);
+ }
+ assert r.current() == ']';
+ r.read(); // skip closing
+ }
+
+ @SuppressWarnings("rawtypes")
+ Class<?> getRawClass(Type type) {
+ if (type instanceof Class)
+ return (Class) type;
+
+ if (type instanceof ParameterizedType)
+ return getRawClass(((ParameterizedType) type).getRawType());
+
+ if (type instanceof GenericArrayType) {
+ Type subType = ((GenericArrayType) type).getGenericComponentType();
+ Class c = getRawClass(subType);
+ return Array.newInstance(c, 0).getClass();
+ }
+
+ throw new IllegalArgumentException(
+ "Does not support generics beyond Parameterized Type and GenericArrayType, got "
+ + type);
+ }
+
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java b/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java
new file mode 100644
index 0000000..5a413b6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java
@@ -0,0 +1,91 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class MapHandler extends Handler {
+ final Class<?> rawClass;
+ final Type keyType;
+ final Type valueType;
+
+ MapHandler(Class<?> rawClass, Type keyType, Type valueType) {
+ this.keyType = keyType;
+ this.valueType = valueType;
+ if (rawClass.isInterface()) {
+ if (rawClass.isAssignableFrom(HashMap.class))
+ rawClass = HashMap.class;
+ else if (rawClass.isAssignableFrom(TreeMap.class))
+ rawClass = TreeMap.class;
+ else if (rawClass.isAssignableFrom(Hashtable.class))
+ rawClass = Hashtable.class;
+ else if (rawClass.isAssignableFrom(LinkedHashMap.class))
+ rawClass = LinkedHashMap.class;
+ else
+ throw new IllegalArgumentException("Unknown map interface: " + rawClass);
+ }
+ this.rawClass = rawClass;
+ }
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ Map<?, ?> map = (Map<?, ?>) object;
+
+ app.append("{");
+ String del = "";
+ for (Map.Entry<?, ?> e : map.entrySet()) {
+ app.append(del);
+ String key;
+ if (e.getKey() != null && (keyType == String.class || keyType == Object.class))
+ key = e.getKey().toString();
+ else {
+ key = app.codec.enc().put(e.getKey()).toString();
+ }
+ StringHandler.string(app, key);
+ app.append(":");
+ app.encode(e.getValue(), valueType, visited);
+ del = ",";
+ }
+ app.append("}");
+ }
+
+ @SuppressWarnings("unchecked") @Override Object decodeObject(Decoder r) throws Exception {
+ assert r.current() == '{';
+
+ Map<Object, Object> map = (Map<Object, Object>) rawClass.newInstance();
+
+ int c = r.next();
+ while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) {
+ Object key = r.codec.parseString(r);
+ if ( !(keyType == null || keyType == Object.class)) {
+ Handler h = r.codec.getHandler(keyType);
+ key = h.decode((String)key);
+ }
+
+ c = r.skipWs();
+ if ( c != ':')
+ throw new IllegalArgumentException("Expected ':' but got " + (char) c);
+
+ c = r.next();
+ Object value = r.codec.decode(valueType, r);
+ map.put(key, value);
+
+ c = r.skipWs();
+
+ if (c == '}')
+ break;
+
+ if (c == ',') {
+ c = r.next();
+ continue;
+ }
+
+ throw new IllegalArgumentException(
+ "Invalid character in parsing list, expected } or , but found " + (char) c);
+ }
+ assert r.current() == '}';
+ r.read(); // skip closing
+ return map;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/NumberHandler.java b/bundleplugin/src/main/java/aQute/lib/json/NumberHandler.java
new file mode 100644
index 0000000..644d49a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/NumberHandler.java
@@ -0,0 +1,71 @@
+package aQute.lib.json;
+
+import java.lang.reflect.*;
+import java.math.*;
+import java.util.*;
+
+public class NumberHandler extends Handler {
+ final Class<?> type;
+
+ NumberHandler(Class<?> clazz) {
+ this.type = clazz;
+ }
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws Exception {
+ String s = object.toString();
+ if ( s.endsWith(".0"))
+ s = s.substring(0,s.length()-2);
+
+ app.append(s);
+ }
+
+ @Override Object decode(boolean s) {
+ return decode(s ? 1d : 0d);
+ }
+
+ @Override Object decode(String s) {
+ double d = Double.parseDouble(s);
+ return decode(d);
+ }
+
+ @Override Object decode() {
+ return decode(0d);
+ }
+
+ @Override Object decode(Number s) {
+ double dd = s.doubleValue();
+
+ if (type == double.class || type == Double.class)
+ return s.doubleValue();
+
+ if ((type == int.class || type == Integer.class)
+ && within(dd, Integer.MIN_VALUE, Integer.MAX_VALUE))
+ return s.intValue();
+
+ if ((type == long.class || type == Long.class) && within(dd, Long.MIN_VALUE, Long.MAX_VALUE))
+ return s.longValue();
+
+ if ((type == byte.class || type == Byte.class) && within(dd, Byte.MIN_VALUE, Byte.MAX_VALUE))
+ return s.byteValue();
+
+ if ((type == short.class || type == Short.class)
+ && within(dd, Short.MIN_VALUE, Short.MAX_VALUE))
+ return s.shortValue();
+
+ if (type == float.class || type == Float.class)
+ return s.floatValue();
+
+ if (type == BigDecimal.class)
+ return BigDecimal.valueOf(dd);
+
+ if (type == BigInteger.class)
+ return BigInteger.valueOf(s.longValue());
+
+ throw new IllegalArgumentException("Unknown number format: " + type);
+ }
+
+ private boolean within(double s, double minValue, double maxValue) {
+ return s >= minValue && s <= maxValue;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java
new file mode 100644
index 0000000..fc6f6bd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java
@@ -0,0 +1,147 @@
+package aQute.lib.json;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+public class ObjectHandler extends Handler {
+ @SuppressWarnings("rawtypes")
+ final Class rawClass;
+ final Field fields[];
+ final Type types[];
+ final Object defaults[];
+ final Field extra;
+
+ ObjectHandler(JSONCodec codec, Class<?> c) throws Exception {
+ rawClass = c;
+ fields = c.getFields();
+
+ // Sort the fields so the output is canonical
+ Arrays.sort(fields, new Comparator<Field>() {
+ public int compare(Field o1, Field o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+
+ types = new Type[fields.length];
+ defaults = new Object[fields.length];
+
+ Field x = null;
+ for (int i = 0; i < fields.length; i++) {
+ if (fields[i].getName().equals("__extra"))
+ x = fields[i];
+ types[i] = fields[i].getGenericType();
+ }
+ if (x != null && Map.class.isAssignableFrom(x.getType()))
+ extra = x;
+ else
+ extra = null;
+
+ try {
+ Object template = c.newInstance();
+
+ for (int i = 0; i < fields.length; i++) {
+ defaults[i] = fields[i].get(template);
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited) throws Exception {
+ app.append("{");
+ String del = "";
+ for (int i = 0; i < fields.length; i++) {
+ if (fields[i].getName().startsWith("__"))
+ continue;
+
+ Object value = fields[i].get(object);
+ if (!app.writeDefaults) {
+ if (value == defaults[i])
+ continue;
+
+ if (value != null && value.equals(defaults[i]))
+ continue;
+ }
+
+ app.append(del);
+ StringHandler.string(app, fields[i].getName());
+ app.append(":");
+ app.encode(value, types[i], visited);
+ del = ",";
+ }
+ app.append("}");
+ }
+
+ @SuppressWarnings("unchecked") @Override Object decodeObject(Decoder r) throws Exception {
+ assert r.current() == '{';
+ Object targetObject = rawClass.newInstance();
+
+ int c = r.next();
+ while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) {
+
+ // Get key
+ String key = r.codec.parseString(r);
+
+ // Get separator
+ c = r.skipWs();
+ if (c != ':')
+ throw new IllegalArgumentException("Expected ':' but got " + (char) c);
+
+ c = r.next();
+
+ // Get value
+
+ Field f = getField(key);
+ if (f != null) {
+ // We have a field and thus a type
+ Object value = r.codec.decode(f.getGenericType(), r);
+ f.set(targetObject, value);
+ } else {
+ // No field, but may extra is defined
+ if (extra == null) {
+ if (r.strict)
+ throw new IllegalArgumentException("No such field " + key);
+ Object value = r.codec.decode(null, r);
+ r.getExtra().put(rawClass.getName() + "." + key, value);
+ } else {
+
+ Map<String, Object> map = (Map<String, Object>) extra.get(targetObject);
+ if (map == null) {
+ map = new LinkedHashMap<String, Object>();
+ extra.set(targetObject, map);
+ }
+ Object value = r.codec.decode(null, r);
+ map.put(key, value);
+ }
+ }
+
+ c = r.skipWs();
+
+ if (c == '}')
+ break;
+
+ if (c == ',') {
+ c = r.next();
+ continue;
+ }
+
+ throw new IllegalArgumentException(
+ "Invalid character in parsing object, expected } or , but found " + (char) c);
+ }
+ assert r.current() == '}';
+ r.read(); // skip closing
+ return targetObject;
+ }
+
+ private Field getField(String key) {
+ for (int i = 0; i < fields.length; i++) {
+ int n = key.compareTo(fields[i].getName());
+ if (n == 0)
+ return fields[i];
+ if (n < 0)
+ return null;
+ }
+ return null;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/SpecialHandler.java b/bundleplugin/src/main/java/aQute/lib/json/SpecialHandler.java
new file mode 100644
index 0000000..33bde8f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/SpecialHandler.java
@@ -0,0 +1,44 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+import java.util.regex.*;
+
+public class SpecialHandler extends Handler {
+ @SuppressWarnings("rawtypes")
+ final Class type;
+ final Method valueOf;
+ final Constructor< ? > constructor;
+ final static SimpleDateFormat sdf = new SimpleDateFormat();
+
+ public SpecialHandler(Class< ? > type, Constructor< ? > constructor,
+ Method valueOf) {
+ this.type = type;
+ this.constructor = constructor;
+ this.valueOf = valueOf;
+ }
+
+ @Override
+ void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException, Exception {
+ StringHandler.string(app, object.toString());
+ }
+
+ @Override
+ Object decode(String s) throws Exception {
+ if (type == Pattern.class)
+ return Pattern.compile(s);
+
+ if (constructor != null)
+ return constructor.newInstance(s);
+
+ if (valueOf != null)
+ return valueOf.invoke(null, s);
+
+ throw new IllegalArgumentException("Do not know how to convert a "
+ + type + " from a string");
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java b/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java
new file mode 100644
index 0000000..2450ed0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java
@@ -0,0 +1,146 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class StringHandler extends Handler {
+
+ @Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+ throws IOException {
+ string(app, object.toString());
+ }
+
+ static void string(Appendable app, String s) throws IOException {
+
+ app.append('"');
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '"':
+ app.append("\\\"");
+ break;
+
+ case '\\':
+ app.append("\\\\");
+ break;
+
+ case '\b':
+ app.append("\\b");
+ break;
+
+ case '\f':
+ app.append("\\f");
+ break;
+
+ case '\n':
+ app.append("\\n");
+ break;
+
+ case '\r':
+ app.append("\\r");
+ break;
+
+ case '\t':
+ app.append("\\t");
+ break;
+
+ default:
+ if (Character.isISOControl(c)) {
+ app.append("\\u");
+ app.append("0123456789ABCDEF".charAt(0xF & (c >> 12)));
+ app.append("0123456789ABCDEF".charAt(0xF & (c >> 8)));
+ app.append("0123456789ABCDEF".charAt(0xF & (c >> 4)));
+ app.append("0123456789ABCDEF".charAt(0xF & (c >> 0)));
+ } else
+ app.append(c);
+ }
+ }
+ app.append('"');
+ }
+
+ Object decode(String s) throws Exception {
+ return s;
+ }
+
+ Object decode(Number s) {
+ return s.toString();
+ }
+
+ Object decode(boolean s) {
+ return Boolean.toString(s);
+ }
+
+ Object decode() {
+ return null;
+ }
+
+ /**
+ * An object can be assigned to a string. This means that the stream is
+ * interpreted as the object but stored in its complete in the string.
+ */
+ Object decodeObject(Decoder r) throws Exception {
+ return collect(r, '}');
+ }
+
+ /**
+ * An array can be assigned to a string. This means that the stream is
+ * interpreted as the array but stored in its complete in the string.
+ */
+ Object decodeArray(Decoder r) throws Exception {
+ return collect(r, ']');
+ }
+
+ /**
+ * Gather the input until you find the the closing character making sure
+ * that new blocks are are take care of.
+ * <p>
+ * This method parses the input for a complete block so that it can be
+ * stored in a string. This allows envelopes.
+ *
+ * @param isr
+ * @param c
+ * @return
+ * @throws Exception
+ */
+ private Object collect(Decoder isr, char close) throws Exception {
+ boolean instring = false;
+ int level = 1;
+ StringBuilder sb = new StringBuilder();
+
+ int c = isr.current();
+ while (c > 0 && level > 0) {
+ sb.append((char) c);
+ if (instring)
+ switch (c) {
+ case '"':
+ instring = true;
+ break;
+
+ case '[':
+ case '{':
+ level++;
+ break;
+
+ case ']':
+ case '}':
+ level--;
+ break;
+ }
+ else
+ switch (c) {
+ case '"':
+ instring = false;
+ break;
+
+ case '\\':
+ sb.append((char) isr.read());
+ break;
+ }
+
+ c = isr.read();
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/packageinfo b/bundleplugin/src/main/java/aQute/lib/json/packageinfo
new file mode 100644
index 0000000..86528b7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/packageinfo
@@ -0,0 +1 @@
+version 2.4.0
diff --git a/bundleplugin/src/main/java/aQute/lib/justif/Justif.java b/bundleplugin/src/main/java/aQute/lib/justif/Justif.java
new file mode 100644
index 0000000..663a9d6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/justif/Justif.java
@@ -0,0 +1,104 @@
+package aQute.lib.justif;
+
+import java.util.*;
+
+
+public class Justif {
+ int []tabs;
+
+ public Justif(int width, int ... tabs) {
+ this.tabs = tabs;
+ }
+
+ /**
+ * Routine to wrap a stringbuffer. Basically adds line endings but has the
+ * following control characters:
+ * <ul>
+ * <li>Space at the beginnng of a line is repeated when wrapped for indent.</li>
+ * <li>A tab will mark the current position and wrapping will return to that
+ * position</li>
+ * <li>A form feed in a tabbed colum will break but stay in the column</li>
+ * </ul>
+ *
+ * @param sb
+ */
+ public void wrap(StringBuilder sb) {
+ List<Integer> indents = new ArrayList<Integer>();
+
+ int indent = 0;
+ int linelength = 0;
+ int lastSpace = 0;
+ int r = 0;
+ boolean begin = true;
+
+ while (r < sb.length()) {
+ switch (sb.charAt(r++)) {
+ case '\n':
+ linelength = 0;
+
+ indent = indents.isEmpty() ? 0 : indents.remove(0);
+ begin = true;
+ lastSpace = 0;
+ break;
+
+ case ' ':
+ if (begin)
+ indent++;
+ lastSpace = r - 1;
+ linelength++;
+ break;
+
+ case '\t':
+ indents.add(indent);
+ indent = linelength;
+ sb.deleteCharAt(--r);
+
+ if (r < sb.length()) {
+ char digit = sb.charAt(r);
+ if (Character.isDigit(digit)) {
+ sb.deleteCharAt(r--);
+
+ int column = (digit - '0');
+ if (column < tabs.length)
+ indent = tabs[column];
+ else
+ indent = column * 8;
+
+ int diff = indent - linelength;
+ if (diff > 0) {
+ for (int i=0; i<diff; i++) {
+ sb.insert(r, ' ');
+ }
+ r += diff;
+ linelength += diff;
+ }
+ }
+ }
+ break;
+
+ case '\f':
+ linelength = 100000; // force a break
+ lastSpace = r-1;
+
+ //$FALL-THROUGH$
+
+ default:
+ linelength++;
+ begin = false;
+ if (lastSpace != 0 && linelength > 60) {
+ sb.setCharAt(lastSpace, '\n');
+ linelength = 0;
+
+ for (int i = 0; i < indent; i++) {
+ sb.insert(lastSpace + 1, ' ');
+ linelength++;
+ }
+ r += indent;
+ lastSpace = 0;
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/justif/packageinfo b/bundleplugin/src/main/java/aQute/lib/justif/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/justif/packageinfo
@@ -0,0 +1 @@
+version 1.0.0
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/About.java b/bundleplugin/src/main/java/aQute/lib/osgi/About.java
new file mode 100755
index 0000000..7265212
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/About.java
@@ -0,0 +1,51 @@
+package aQute.lib.osgi;
+
+import aQute.libg.header.*;
+
+/**
+ * This package contains a number of classes that assists by analyzing JARs and
+ * constructing bundles.
+ *
+ * The Analyzer class can be used to analyze an existing bundle and can create a
+ * manifest specification from proposed (wildcard) Export-Package,
+ * Bundle-Includes, and Import-Package headers.
+ *
+ * The Builder class can use the headers to construct a JAR from the classpath.
+ *
+ * The Verifier class can take an existing JAR and verify that all headers are
+ * correctly set. It will verify the syntax of the headers, match it against the
+ * proper contents, and verify imports and exports.
+ *
+ * A number of utility classes are available.
+ *
+ * Jar, provides an abstraction of a Jar file. It has constructors for creating
+ * a Jar from a stream, a directory, or a jar file. A Jar, keeps a collection
+ * Resource's. There are Resource implementations for File, from ZipFile, or
+ * from a stream (which copies the data). The Jar tries to minimize the work
+ * during build up so that it is cheap to use. The Resource's can be used to
+ * iterate over the names and later read the resources when needed.
+ *
+ * Clazz, provides a parser for the class files. This will be used to define the
+ * imports and exports.
+ *
+ * Headers are translated to {@link Parameters} that contains all headers (the
+ * order is maintained). The attribute of each header are maintained in an
+ * {@link Attrs}. Each additional file in a header definition will have its own
+ * entry (only native code does not work this way). The ':' of directives is
+ * considered part of the name. This allows attributes and directives to be
+ * maintained in the Attributes map.
+ *
+ * An important aspect of the specification is to allow the use of wildcards.
+ * Wildcards select from a set and can decorate the entries with new attributes.
+ * This functionality is implemented in Instructions.
+ *
+ * Much of the information calculated is in packages. A package is identified
+ * by a PackageRef (and a type by a TypeRef). The namespace is maintained
+ * by {@link Descriptors}, which here is owned by {@link Analyzer}. A special
+ * class, {@link Packages} maintains the attributes that are found in the code.
+ *
+ * @version $Revision$
+ */
+public class About {
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/AbstractResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/AbstractResource.java
new file mode 100644
index 0000000..4c52103
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/AbstractResource.java
@@ -0,0 +1,54 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public abstract class AbstractResource implements Resource {
+ String extra;
+ byte[] calculated;
+ long lastModified;
+
+ protected AbstractResource(long modified) {
+ lastModified = modified;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return new ByteArrayInputStream(getLocalBytes());
+ }
+
+ private byte[] getLocalBytes() throws IOException {
+ try {
+ if (calculated != null)
+ return calculated;
+
+ return calculated = getBytes();
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ IOException ee = new IOException("Opening resource");
+ ee.initCause(e);
+ throw ee;
+ }
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public void write(OutputStream out) throws IOException {
+ out.write(getLocalBytes());
+ }
+
+ abstract protected byte[] getBytes() throws Exception;
+
+ public long size() throws IOException {
+ return getLocalBytes().length;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Analyzer.java b/bundleplugin/src/main/java/aQute/lib/osgi/Analyzer.java
new file mode 100755
index 0000000..8be2edb
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Analyzer.java
@@ -0,0 +1,2486 @@
+package aQute.lib.osgi;
+
+/**
+ * This class can calculate the required headers for a (potential) JAR file. It
+ * analyzes a directory or JAR for the packages that are contained and that are
+ * referred to by the bytecodes. The user can the use regular expressions to
+ * define the attributes and directives. The matching is not fully regex for
+ * convenience. A * and ? get a . prefixed and dots are escaped.
+ *
+ * <pre>
+ * *;auto=true any
+ * org.acme.*;auto=true org.acme.xyz
+ * org.[abc]*;auto=true org.acme.xyz
+ * </pre>
+ *
+ * Additional, the package instruction can start with a '=' or a '!'. The '!'
+ * indicates negation. Any matching package is removed. The '=' is literal, the
+ * expression will be copied verbatim and no matching will take place.
+ *
+ * Any headers in the given properties are used in the output properties.
+ */
+import static aQute.libg.generics.Create.*;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.jar.Attributes.Name;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.*;
+import aQute.bnd.service.*;
+import aQute.lib.base64.*;
+import aQute.lib.collections.*;
+import aQute.lib.filter.*;
+import aQute.lib.hex.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.Descriptors.Descriptor;
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.cryptography.*;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.version.Version;
+
+public class Analyzer extends Processor {
+ private final SortedSet<Clazz.JAVA> ees = new TreeSet<Clazz.JAVA>();
+ static Properties bndInfo;
+
+ // Bundle parameters
+ private Jar dot;
+ private final Packages contained = new Packages();
+ private final Packages referred = new Packages();
+ private Packages exports;
+ private Packages imports;
+ private TypeRef activator;
+
+ // Global parameters
+ private final MultiMap<PackageRef, PackageRef> uses = new MultiMap<PackageRef, PackageRef>(
+ PackageRef.class,
+ PackageRef.class,
+ true);
+ private final Packages classpathExports = new Packages();
+ private final Descriptors descriptors = new Descriptors();
+ private final List<Jar> classpath = list();
+ private final Map<TypeRef, Clazz> classspace = map();
+ private final Map<TypeRef, Clazz> importedClassesCache = map();
+ private boolean analyzed = false;
+ private boolean diagnostics = false;
+ private boolean inited = false;
+
+ public Analyzer(Processor parent) {
+ super(parent);
+ }
+
+ public Analyzer() {
+ }
+
+ /**
+ * Specifically for Maven
+ *
+ * @param properties the properties
+ */
+
+ public static Properties getManifest(File dirOrJar) throws Exception {
+ Analyzer analyzer = new Analyzer();
+ try {
+ analyzer.setJar(dirOrJar);
+ Properties properties = new Properties();
+ properties.put(IMPORT_PACKAGE, "*");
+ properties.put(EXPORT_PACKAGE, "*");
+ analyzer.setProperties(properties);
+ Manifest m = analyzer.calcManifest();
+ Properties result = new Properties();
+ for (Iterator<Object> i = m.getMainAttributes().keySet().iterator(); i.hasNext();) {
+ Attributes.Name name = (Attributes.Name) i.next();
+ result.put(name.toString(), m.getMainAttributes().getValue(name));
+ }
+ return result;
+ }
+ finally {
+ analyzer.close();
+ }
+ }
+
+ /**
+ * Calculates the data structures for generating a manifest.
+ *
+ * @throws IOException
+ */
+ public void analyze() throws Exception {
+ if (!analyzed) {
+ analyzed = true;
+ uses.clear();
+ classspace.clear();
+ classpathExports.clear();
+
+ // Parse all the class in the
+ // the jar according to the OSGi bcp
+ analyzeBundleClasspath();
+
+ //
+ // calculate class versions in use
+ //
+ for (Clazz c : classspace.values()) {
+ ees.add(c.getFormat());
+ }
+
+ //
+ // Get exported packages from the
+ // entries on the classpath
+ //
+
+ for (Jar current : getClasspath()) {
+ getExternalExports(current, classpathExports);
+ for (String dir : current.getDirectories().keySet()) {
+ PackageRef packageRef = getPackageRef(dir);
+ Resource resource = current.getResource(dir + "/packageinfo");
+ setPackageInfo(packageRef, resource, classpathExports);
+ }
+ }
+
+ // Handle the bundle activator
+
+ String s = getProperty(BUNDLE_ACTIVATOR);
+ if (s != null) {
+ activator = getTypeRefFromFQN(s);
+ referTo(activator);
+ }
+
+ // Execute any plugins
+ // TODO handle better reanalyze
+ doPlugins();
+
+ Jar extra = getExtra();
+ while (extra != null) {
+ dot.addAll(extra);
+ analyzeJar(extra, "", true);
+ extra = getExtra();
+ }
+
+ referred.keySet().removeAll(contained.keySet());
+
+ //
+ // EXPORTS
+ //
+ {
+ Set<Instruction> unused = Create.set();
+
+ Instructions filter = new Instructions(getExportPackage());
+ filter.append(getExportContents());
+
+ exports = filter(filter, contained, unused);
+
+ if (!unused.isEmpty()) {
+ warning("Unused Export-Package instructions: %s ", unused);
+ }
+
+ // See what information we can find to augment the
+ // exports. I.e. look on the classpath
+ augmentExports(exports);
+ }
+
+ //
+ // IMPORTS
+ // Imports MUST come after exports because we use information from
+ // the exports
+ //
+ {
+ // Add all exports that do not have an -noimport: directive
+ // to the imports.
+ Packages referredAndExported = new Packages(referred);
+ referredAndExported.putAll(doExportsToImports(exports));
+
+ removeDynamicImports(referredAndExported);
+
+ // Remove any Java references ... where are the closures???
+ for (Iterator<PackageRef> i = referredAndExported.keySet().iterator(); i.hasNext();) {
+ if (i.next().isJava())
+ i.remove();
+ }
+
+ Set<Instruction> unused = Create.set();
+ String h = getProperty(IMPORT_PACKAGE);
+ if (h == null) // If not set use a default
+ h = "*";
+
+ if (isPedantic() && h.trim().length() == 0)
+ warning("Empty Import-Package header");
+
+ Instructions filter = new Instructions(h);
+ imports = filter(filter, referredAndExported, unused);
+ if (!unused.isEmpty()) {
+ // We ignore the end wildcard catch
+ if (!(unused.size() == 1 && unused.iterator().next().toString().equals("*")))
+ warning("Unused Import-Package instructions: %s ", unused);
+ }
+
+ // See what information we can find to augment the
+ // imports. I.e. look in the exports
+ augmentImports(imports, exports);
+ }
+
+ //
+ // USES
+ //
+ // Add the uses clause to the exports
+ doUses(exports, uses, imports);
+
+ //
+ // Checks
+ //
+ if (referred.containsKey(Descriptors.DEFAULT_PACKAGE)) {
+ error("The default package '.' is not permitted by the Import-Package syntax. \n"
+ + " This can be caused by compile errors in Eclipse because Eclipse creates \n"
+ + "valid class files regardless of compile errors.\n"
+ + "The following package(s) import from the default package "
+ + uses.transpose().get(Descriptors.DEFAULT_PACKAGE));
+ }
+
+ }
+ }
+
+ /**
+ * Discussed with BJ and decided to kill the .
+ *
+ * @param referredAndExported
+ */
+ void removeDynamicImports(Packages referredAndExported) {
+
+ // // Remove any matching a dynamic import package instruction
+ // Instructions dynamicImports = new
+ // Instructions(getDynamicImportPackage());
+ // Collection<PackageRef> dynamic = dynamicImports.select(
+ // referredAndExported.keySet(), false);
+ // referredAndExported.keySet().removeAll(dynamic);
+ }
+
+ protected Jar getExtra() throws Exception {
+ return null;
+ }
+
+ /**
+ *
+ */
+ void doPlugins() {
+ for (AnalyzerPlugin plugin : getPlugins(AnalyzerPlugin.class)) {
+ try {
+ boolean reanalyze = plugin.analyzeJar(this);
+ if (reanalyze) {
+ classspace.clear();
+ analyzeBundleClasspath();
+ }
+ }
+ catch (Exception e) {
+ error("Analyzer Plugin %s failed %s", plugin, e);
+ }
+ }
+ }
+
+ /**
+ *
+ * @return
+ */
+ boolean isResourceOnly() {
+ return isTrue(getProperty(RESOURCEONLY));
+ }
+
+ /**
+ * One of the main workhorses of this class. This will analyze the current
+ * setp and calculate a new manifest according to this setup. This method
+ * will also set the manifest on the main jar dot
+ *
+ * @return
+ * @throws IOException
+ */
+ public Manifest calcManifest() throws Exception {
+ analyze();
+ Manifest manifest = new Manifest();
+ Attributes main = manifest.getMainAttributes();
+
+ main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ main.putValue(BUNDLE_MANIFESTVERSION, "2");
+
+ boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
+
+ if (!noExtraHeaders) {
+ main.putValue(CREATED_BY,
+ System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
+ + ")");
+ main.putValue(TOOL, "Bnd-" + getBndVersion());
+ main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
+ }
+
+ String exportHeader = printClauses(exports, true);
+
+ if (exportHeader.length() > 0)
+ main.putValue(EXPORT_PACKAGE, exportHeader);
+ else
+ main.remove(EXPORT_PACKAGE);
+
+ // Remove all the Java packages from the imports
+ if (!imports.isEmpty()) {
+ main.putValue(IMPORT_PACKAGE, printClauses(imports));
+ }
+ else {
+ main.remove(IMPORT_PACKAGE);
+ }
+
+ Packages temp = new Packages(contained);
+ temp.keySet().removeAll(exports.keySet());
+
+ if (!temp.isEmpty())
+ main.putValue(PRIVATE_PACKAGE, printClauses(temp));
+ else
+ main.remove(PRIVATE_PACKAGE);
+
+ Parameters bcp = getBundleClasspath();
+ if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
+ main.remove(BUNDLE_CLASSPATH);
+ else
+ main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
+
+ doNamesection(dot, manifest);
+
+ for (Enumeration< ? > h = getProperties().propertyNames(); h.hasMoreElements();) {
+ String header = (String) h.nextElement();
+ if (header.trim().length() == 0) {
+ warning("Empty property set with value: " + getProperties().getProperty(header));
+ continue;
+ }
+
+ if (isMissingPlugin(header.trim())) {
+ error("Missing plugin for command %s", header);
+ }
+ if (!Character.isUpperCase(header.charAt(0))) {
+ if (header.charAt(0) == '@')
+ doNameSection(manifest, header);
+ continue;
+ }
+
+ if (header.equals(BUNDLE_CLASSPATH) || header.equals(EXPORT_PACKAGE)
+ || header.equals(IMPORT_PACKAGE))
+ continue;
+
+ if (header.equalsIgnoreCase("Name")) {
+ error("Your bnd file contains a header called 'Name'. This interferes with the manifest name section.");
+ continue;
+ }
+
+ if (Verifier.HEADER_PATTERN.matcher(header).matches()) {
+ String value = getProperty(header);
+ if (value != null && main.getValue(header) == null) {
+ if (value.trim().length() == 0)
+ main.remove(header);
+ else
+ if (value.trim().equals(EMPTY_HEADER))
+ main.putValue(header, "");
+ else
+ main.putValue(header, value);
+ }
+ }
+ else {
+ // TODO should we report?
+ }
+ }
+
+ //
+ // Calculate the bundle symbolic name if it is
+ // not set.
+ // 1. set
+ // 2. name of properties file (must be != bnd.bnd)
+ // 3. name of directory, which is usualy project name
+ //
+ String bsn = getBsn();
+ if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
+ main.putValue(BUNDLE_SYMBOLICNAME, bsn);
+ }
+
+ //
+ // Use the same name for the bundle name as BSN when
+ // the bundle name is not set
+ //
+ if (main.getValue(BUNDLE_NAME) == null) {
+ main.putValue(BUNDLE_NAME, bsn);
+ }
+
+ if (main.getValue(BUNDLE_VERSION) == null)
+ main.putValue(BUNDLE_VERSION, "0");
+
+ // Copy old values into new manifest, when they
+ // exist in the old one, but not in the new one
+ merge(manifest, dot.getManifest());
+
+ // Remove all the headers mentioned in -removeheaders
+ Instructions instructions = new Instructions(getProperty(REMOVEHEADERS));
+ Collection<Object> result = instructions.select(main.keySet(), false);
+ main.keySet().removeAll(result);
+
+ dot.setManifest(manifest);
+ return manifest;
+ }
+
+ /**
+ * Parse the namesection as instructions and then match them against the
+ * current set of resources
+ *
+ * For example:
+ *
+ * <pre>
+ * -namesection: *;baz=true, abc/def/bar/X.class=3
+ * </pre>
+ *
+ * The raw value of {@link Constants#NAMESECTION} is used but the values of
+ * the attributes are replaced where @ is set to the resource name. This
+ * allows macro to operate on the resource
+ *
+ */
+
+ private void doNamesection(Jar dot, Manifest manifest) {
+
+ Parameters namesection = parseHeader(getProperties().getProperty(NAMESECTION));
+ Instructions instructions = new Instructions(namesection);
+ Set<String> resources = new HashSet<String>(dot.getResources().keySet());
+
+ //
+ // For each instruction, iterator over the resources and filter
+ // them. If a resource matches, it must be removed even if the
+ // instruction is negative. If positive, add a name section
+ // to the manifest for the given resource name. Then add all
+ // attributes from the instruction to that name section.
+ //
+ for (Map.Entry<Instruction, Attrs> instr : instructions.entrySet()) {
+ boolean matched = false;
+
+ // For each instruction
+
+ for (Iterator<String> i = resources.iterator(); i.hasNext();) {
+ String path = i.next();
+ // For each resource
+
+ if (instr.getKey().matches(path)) {
+
+ // Instruction matches the resource
+
+ matched = true;
+ if (!instr.getKey().isNegated()) {
+
+ // Positive match, add the attributes
+
+ Attributes attrs = manifest.getAttributes(path);
+ if (attrs == null) {
+ attrs = new Attributes();
+ manifest.getEntries().put(path, attrs);
+ }
+
+ //
+ // Add all the properties from the instruction to the
+ // name section
+ //
+
+ for (Map.Entry<String, String> property : instr.getValue().entrySet()) {
+ setProperty("@", path);
+ try {
+ String processed = getReplacer().process(property.getValue());
+ attrs.putValue(property.getKey(), processed);
+ }
+ finally {
+ unsetProperty("@");
+ }
+ }
+ }
+ i.remove();
+ }
+ }
+
+ if (!matched && resources.size() > 0)
+ warning("The instruction %s in %s did not match any resources", instr.getKey(),
+ NAMESECTION);
+ }
+
+ }
+
+ /**
+ * This method is called when the header starts with a @, signifying a name
+ * section header. The name part is defined by replacing all the @ signs to
+ * a /, removing the first and the last, and using the last part as header
+ * name:
+ *
+ * <pre>
+ * @org@osgi@service@event@Implementation-Title
+ * </pre>
+ *
+ * This will be the header Implementation-Title in the
+ * org/osgi/service/event named section.
+ *
+ * @param manifest
+ * @param header
+ */
+ private void doNameSection(Manifest manifest, String header) {
+ String path = header.replace('@', '/');
+ int n = path.lastIndexOf('/');
+ // Must succeed because we start with @
+ String name = path.substring(n + 1);
+ // Skip first /
+ path = path.substring(1, n);
+ if (name.length() != 0 && path.length() != 0) {
+ Attributes attrs = manifest.getAttributes(path);
+ if (attrs == null) {
+ attrs = new Attributes();
+ manifest.getEntries().put(path, attrs);
+ }
+ attrs.putValue(name, getProperty(header));
+ }
+ else {
+ warning("Invalid header (starts with @ but does not seem to be for the Name section): %s",
+ header);
+ }
+ }
+
+ /**
+ * Clear the key part of a header. I.e. remove everything from the first ';'
+ *
+ * @param value
+ * @return
+ */
+ public String getBsn() {
+ String value = getProperty(BUNDLE_SYMBOLICNAME);
+ if (value == null) {
+ if (getPropertiesFile() != null)
+ value = getPropertiesFile().getName();
+
+ String projectName = getBase().getName();
+ if (value == null || value.equals("bnd.bnd")) {
+ value = projectName;
+ }
+ else
+ if (value.endsWith(".bnd")) {
+ value = value.substring(0, value.length() - 4);
+ if (!value.startsWith(getBase().getName()))
+ value = projectName + "." + value;
+ }
+ }
+
+ if (value == null)
+ return "untitled";
+
+ int n = value.indexOf(';');
+ if (n > 0)
+ value = value.substring(0, n);
+ return value.trim();
+ }
+
+ public String _bsn(String args[]) {
+ return getBsn();
+ }
+
+ /**
+ * Calculate an export header solely based on the contents of a JAR file
+ *
+ * @param bundle The jar file to analyze
+ * @return
+ */
+ public String calculateExportsFromContents(Jar bundle) {
+ String ddel = "";
+ StringBuilder sb = new StringBuilder();
+ Map<String, Map<String, Resource>> map = bundle.getDirectories();
+ for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
+ String directory = i.next();
+ if (directory.equals("META-INF") || directory.startsWith("META-INF/"))
+ continue;
+ if (directory.equals("OSGI-OPT") || directory.startsWith("OSGI-OPT/"))
+ continue;
+ if (directory.equals("/"))
+ continue;
+
+ if (directory.endsWith("/"))
+ directory = directory.substring(0, directory.length() - 1);
+
+ directory = directory.replace('/', '.');
+ sb.append(ddel);
+ sb.append(directory);
+ ddel = ",";
+ }
+ return sb.toString();
+ }
+
+ public Packages getContained() {
+ return contained;
+ }
+
+ public Packages getExports() {
+ return exports;
+ }
+
+ public Packages getImports() {
+ return imports;
+ }
+
+ public Jar getJar() {
+ return dot;
+ }
+
+ public Packages getReferred() {
+ return referred;
+ }
+
+ /**
+ * Return the set of unreachable code depending on exports and the bundle
+ * activator.
+ *
+ * @return
+ */
+ public Set<PackageRef> getUnreachable() {
+ Set<PackageRef> unreachable = new HashSet<PackageRef>(uses.keySet()); // all
+ for (Iterator<PackageRef> r = exports.keySet().iterator(); r.hasNext();) {
+ PackageRef packageRef = r.next();
+ removeTransitive(packageRef, unreachable);
+ }
+ if (activator != null) {
+ removeTransitive(activator.getPackageRef(), unreachable);
+ }
+ return unreachable;
+ }
+
+ public MultiMap<PackageRef, PackageRef> getUses() {
+ return uses;
+ }
+
+ /**
+ * Get the version for this bnd
+ *
+ * @return version or unknown.
+ */
+ public String getBndVersion() {
+ return getBndInfo("version", "1.42.1");
+ }
+
+ public long getBndLastModified() {
+ String time = getBndInfo("modified", "0");
+ try {
+ return Long.parseLong(time);
+ }
+ catch (Exception e) {
+ }
+ return 0;
+ }
+
+ public String getBndInfo(String key, String defaultValue) {
+ synchronized (Analyzer.class) {
+ if (bndInfo == null) {
+ bndInfo = new Properties();
+ InputStream in = Analyzer.class.getResourceAsStream("bnd.info");
+ try {
+ if (in != null) {
+ bndInfo.load(in);
+ in.close();
+ }
+ }
+ catch (IOException ioe) {
+ warning("Could not read bnd.info in " + Analyzer.class.getPackage() + ioe);
+ }
+ finally {
+ IO.close(in);
+ }
+ }
+ }
+ return bndInfo.getProperty(key, defaultValue);
+ }
+
+ /**
+ * Merge the existing manifest with the instructions but do not override
+ * existing properties.
+ *
+ * @param manifest The manifest to merge with
+ * @throws IOException
+ */
+ public void mergeManifest(Manifest manifest) throws IOException {
+ if (manifest != null) {
+ Attributes attributes = manifest.getMainAttributes();
+ for (Iterator<Object> i = attributes.keySet().iterator(); i.hasNext();) {
+ Name name = (Name) i.next();
+ String key = name.toString();
+ // Dont want instructions
+ if (key.startsWith("-"))
+ continue;
+
+ if (getProperty(key) == null)
+ setProperty(key, attributes.getValue(name));
+ }
+ }
+ }
+
+ public void setBase(File file) {
+ super.setBase(file);
+ getProperties().put("project.dir", getBase().getAbsolutePath());
+ }
+
+ /**
+ * Set the classpath for this analyzer by file.
+ *
+ * @param classpath
+ * @throws IOException
+ */
+ public void setClasspath(File[] classpath) throws IOException {
+ List<Jar> list = new ArrayList<Jar>();
+ for (int i = 0; i < classpath.length; i++) {
+ if (classpath[i].exists()) {
+ Jar current = new Jar(classpath[i]);
+ list.add(current);
+ }
+ else {
+ error("Missing file on classpath: %s", classpath[i]);
+ }
+ }
+ for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
+ addClasspath(i.next());
+ }
+ }
+
+ public void setClasspath(Jar[] classpath) {
+ for (int i = 0; i < classpath.length; i++) {
+ addClasspath(classpath[i]);
+ }
+ }
+
+ public void setClasspath(String[] classpath) {
+ for (int i = 0; i < classpath.length; i++) {
+ Jar jar = getJarFromName(classpath[i], " setting classpath");
+ if (jar != null)
+ addClasspath(jar);
+ }
+ }
+
+ /**
+ * Set the JAR file we are going to work in. This will read the JAR in
+ * memory.
+ *
+ * @param jar
+ * @return
+ * @throws IOException
+ */
+ public Jar setJar(File jar) throws IOException {
+ Jar jarx = new Jar(jar);
+ addClose(jarx);
+ return setJar(jarx);
+ }
+
+ /**
+ * Set the JAR directly we are going to work on.
+ *
+ * @param jar
+ * @return
+ */
+ public Jar setJar(Jar jar) {
+ if (dot != null)
+ removeClose(dot);
+
+ this.dot = jar;
+ if (dot != null)
+ addClose(dot);
+
+ return jar;
+ }
+
+ protected void begin() {
+ if (inited == false) {
+ inited = true;
+ super.begin();
+
+ updateModified(getBndLastModified(), "bnd last modified");
+ verifyManifestHeadersCase(getProperties());
+
+ }
+ }
+
+ /**
+ * Try to get a Jar from a file name/path or a url, or in last resort from
+ * the classpath name part of their files.
+ *
+ * @param name URL or filename relative to the base
+ * @param from Message identifying the caller for errors
+ * @return null or a Jar with the contents for the name
+ */
+ Jar getJarFromName(String name, String from) {
+ File file = new File(name);
+ if (!file.isAbsolute())
+ file = new File(getBase(), name);
+
+ if (file.exists())
+ try {
+ Jar jar = new Jar(file);
+ addClose(jar);
+ return jar;
+ }
+ catch (Exception e) {
+ error("Exception in parsing jar file for " + from + ": " + name + " " + e);
+ }
+ // It is not a file ...
+ try {
+ // Lets try a URL
+ URL url = new URL(name);
+ Jar jar = new Jar(fileName(url.getPath()));
+ addClose(jar);
+ URLConnection connection = url.openConnection();
+ InputStream in = connection.getInputStream();
+ long lastModified = connection.getLastModified();
+ if (lastModified == 0)
+ // We assume the worst :-(
+ lastModified = System.currentTimeMillis();
+ EmbeddedResource.build(jar, in, lastModified);
+ in.close();
+ return jar;
+ }
+ catch (IOException ee) {
+ // Check if we have files on the classpath
+ // that have the right name, allows us to specify those
+ // names instead of the full path.
+ for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
+ Jar entry = cp.next();
+ if (entry.getSource() != null && entry.getSource().getName().equals(name)) {
+ return entry;
+ }
+ }
+ // error("Can not find jar file for " + from + ": " + name);
+ }
+ return null;
+ }
+
+ private String fileName(String path) {
+ int n = path.lastIndexOf('/');
+ if (n > 0)
+ return path.substring(n + 1);
+ return path;
+ }
+
+ /**
+ *
+ * @param manifests
+ * @throws Exception
+ */
+ private void merge(Manifest result, Manifest old) throws IOException {
+ if (old != null) {
+ for (Iterator<Map.Entry<Object, Object>> e = old.getMainAttributes().entrySet()
+ .iterator(); e.hasNext();) {
+ Map.Entry<Object, Object> entry = e.next();
+ Attributes.Name name = (Attributes.Name) entry.getKey();
+ String value = (String) entry.getValue();
+ if (name.toString().equalsIgnoreCase("Created-By"))
+ name = new Attributes.Name("Originally-Created-By");
+ if (!result.getMainAttributes().containsKey(name))
+ result.getMainAttributes().put(name, value);
+ }
+
+ // do not overwrite existing entries
+ Map<String, Attributes> oldEntries = old.getEntries();
+ Map<String, Attributes> newEntries = result.getEntries();
+ for (Iterator<Map.Entry<String, Attributes>> e = oldEntries.entrySet().iterator(); e
+ .hasNext();) {
+ Map.Entry<String, Attributes> entry = e.next();
+ if (!newEntries.containsKey(entry.getKey())) {
+ newEntries.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ /**
+ * Bnd is case sensitive for the instructions so we better check people are
+ * not using an invalid case. We do allow this to set headers that should
+ * not be processed by us but should be used by the framework.
+ *
+ * @param properties Properties to verify.
+ */
+
+ void verifyManifestHeadersCase(Properties properties) {
+ for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
+ String header = (String) i.next();
+ for (int j = 0; j < headers.length; j++) {
+ if (!headers[j].equals(header) && headers[j].equalsIgnoreCase(header)) {
+ warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
+ + header + " and expecting: " + headers[j]);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * We will add all exports to the imports unless there is a -noimport
+ * directive specified on an export. This directive is skipped for the
+ * manifest.
+ *
+ * We also remove any version parameter so that augmentImports can do the
+ * version policy.
+ *
+ * The following method is really tricky and evolved over time. Coming from
+ * the original background of OSGi, it was a weird idea for me to have a
+ * public package that should not be substitutable. I was so much convinced
+ * that this was the right rule that I rücksichtlos imported them all. Alas,
+ * the real world was more subtle than that. It turns out that it is not a
+ * good idea to always import. First, there must be a need to import, i.e.
+ * there must be a contained package that refers to the exported package for
+ * it to make use importing that package. Second, if an exported package
+ * refers to an internal package than it should not be imported.
+ *
+ * Additionally, it is necessary to treat the exports in groups. If an
+ * exported package refers to another exported packages than it must be in
+ * the same group. A framework can only substitute exports for imports for
+ * the whole of such a group. WHY????? Not clear anymore ...
+ *
+ */
+ Packages doExportsToImports(Packages exports) {
+
+ // private packages = contained - exported.
+ Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
+ privatePackages.removeAll(exports.keySet());
+
+ // private references = ∀ p : private packages | uses(p)
+ Set<PackageRef> privateReferences = newSet();
+ for (PackageRef p : privatePackages) {
+ Collection<PackageRef> uses = this.uses.get(p);
+ if (uses != null)
+ privateReferences.addAll(uses);
+ }
+
+ // Assume we are going to export all exported packages
+ Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
+
+ // Remove packages that are not referenced privately
+ toBeImported.retainAll(privateReferences);
+
+ // Not necessary to import anything that is already
+ // imported in the Import-Package statement.
+ // TODO toBeImported.removeAll(imports.keySet());
+
+ // Remove exported packages that are referring to
+ // private packages.
+ // Each exported package has a uses clause. We just use
+ // the used packages for each exported package to find out
+ // if it refers to an internal package.
+ //
+
+ for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
+ PackageRef next = i.next();
+ Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
+
+ for (PackageRef privatePackage : privatePackages) {
+ if (usedByExportedPackage.contains(privatePackage)) {
+ i.remove();
+ break;
+ }
+ }
+ }
+
+ // Clean up attributes and generate result map
+ Packages result = new Packages();
+ for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
+ PackageRef ep = i.next();
+ Attrs parameters = exports.get(ep);
+
+ String noimport = parameters.get(NO_IMPORT_DIRECTIVE);
+ if (noimport != null && noimport.equalsIgnoreCase("true"))
+ continue;
+
+ // // we can't substitute when there is no version
+ // String version = parameters.get(VERSION_ATTRIBUTE);
+ // if (version == null) {
+ // if (isPedantic())
+ // warning(
+ // "Cannot automatically import exported package %s because it has no version defined",
+ // ep);
+ // continue;
+ // }
+
+ parameters = new Attrs();
+ parameters.remove(VERSION_ATTRIBUTE);
+ result.put(ep, parameters);
+ }
+ return result;
+ }
+
+ public boolean referred(PackageRef packageName) {
+ // return true;
+ for (Map.Entry<PackageRef, List<PackageRef>> contained : uses.entrySet()) {
+ if (!contained.getKey().equals(packageName)) {
+ if (contained.getValue().contains(packageName))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @param jar
+ */
+ private void getExternalExports(Jar jar, Packages classpathExports) {
+ try {
+ Manifest m = jar.getManifest();
+ if (m != null) {
+ Domain domain = Domain.domain(m);
+ Parameters exported = domain.getExportPackage();
+ for (Entry<String, Attrs> e : exported.entrySet()) {
+ PackageRef ref = getPackageRef(e.getKey());
+ if (!classpathExports.containsKey(ref)) {
+ // TODO e.getValue().put(SOURCE_DIRECTIVE,
+ // jar.getBsn()+"-"+jar.getVersion());
+
+ classpathExports.put(ref, e.getValue());
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ warning("Erroneous Manifest for " + jar + " " + e);
+ }
+ }
+
+ /**
+ * Find some more information about imports in manifest and other places. It
+ * is assumed that the augmentsExports has already copied external attrs
+ * from the classpathExports.
+ *
+ * @throws Exception
+ */
+ void augmentImports(Packages imports, Packages exports) throws Exception {
+ List<PackageRef> noimports = Create.list();
+ Set<PackageRef> provided = findProvidedPackages();
+
+ for (PackageRef packageRef : imports.keySet()) {
+ String packageName = packageRef.getFQN();
+
+ setProperty(CURRENT_PACKAGE, packageName);
+ try {
+ Attrs importAttributes = imports.get(packageRef);
+ Attrs exportAttributes = exports.get(packageRef,
+ classpathExports.get(packageRef, new Attrs()));
+
+ String exportVersion = exportAttributes.getVersion();
+ String importRange = importAttributes.getVersion();
+
+ if (exportVersion == null) {
+ // TODO Should check if the source is from a bundle.
+
+ }
+ else {
+
+ //
+ // Version Policy - Import version substitution. We
+ // calculate the export version and then allow the
+ // import version attribute to use it in a substitution
+ // by using a ${@} macro. The export version can
+ // be defined externally or locally
+ //
+
+ boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
+ || isTrue(exportAttributes.get(PROVIDE_DIRECTIVE))
+ || provided.contains(packageRef);
+
+ exportVersion = cleanupVersion(exportVersion);
+
+ try {
+ setProperty("@", exportVersion);
+
+ if (importRange != null) {
+ importRange = cleanupVersion(importRange);
+ importRange = getReplacer().process(importRange);
+ }
+ else
+ importRange = getVersionPolicy(provider);
+
+ }
+ finally {
+ unsetProperty("@");
+ }
+ importAttributes.put(VERSION_ATTRIBUTE, importRange);
+ }
+
+ //
+ // Check if exporter has mandatory attributes
+ //
+ String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
+ if (mandatory != null) {
+ String[] attrs = mandatory.split("\\s*,\\s*");
+ for (int i = 0; i < attrs.length; i++) {
+ if (!importAttributes.containsKey(attrs[i]))
+ importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
+ }
+ }
+
+ if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
+ importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
+
+ fixupAttributes(importAttributes);
+ removeAttributes(importAttributes);
+
+ String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
+ if (result == null)
+ noimports.add(packageRef);
+ }
+ finally {
+ unsetProperty(CURRENT_PACKAGE);
+ }
+ }
+
+ if (isPedantic() && noimports.size() != 0) {
+ warning("Imports that lack version ranges: %s", noimports);
+ }
+ }
+
+ /**
+ * Find the packages we depend on, where we implement an interface that is a
+ * Provider Type. These packages, when we import them, must use the provider
+ * policy.
+ *
+ * @throws Exception
+ */
+ Set<PackageRef> findProvidedPackages() throws Exception {
+ Set<PackageRef> providers = Create.set();
+ Set<TypeRef> cached = Create.set();
+
+ for (Clazz c : classspace.values()) {
+ TypeRef[] interfaces = c.getInterfaces();
+ if (interfaces != null)
+ for (TypeRef t : interfaces)
+ if (cached.contains(t) || isProvider(t)) {
+ cached.add(t);
+ providers.add(t.getPackageRef());
+ }
+ }
+ return providers;
+ }
+
+ private boolean isProvider(TypeRef t) throws Exception {
+ Clazz c = findClass(t);
+ if (c == null)
+ return false;
+
+ if (c.annotations == null)
+ return false;
+
+ TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
+ boolean result = c.annotations.contains(pt);
+ return result;
+ }
+
+ /**
+ * Provide any macro substitutions and versions for exported packages.
+ */
+
+ void augmentExports(Packages exports) {
+ for (PackageRef packageRef : exports.keySet()) {
+ String packageName = packageRef.getFQN();
+ setProperty(CURRENT_PACKAGE, packageName);
+ try {
+ Attrs attributes = exports.get(packageRef);
+ Attrs exporterAttributes = classpathExports.get(packageRef);
+ if (exporterAttributes == null)
+ continue;
+
+ for (Map.Entry<String, String> entry : exporterAttributes.entrySet()) {
+ String key = entry.getKey();
+ if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
+ key = VERSION_ATTRIBUTE;
+
+ // dont overwrite and no directives
+ if (!key.endsWith(":") && !attributes.containsKey(key)) {
+ attributes.put(key, entry.getValue());
+ }
+ }
+
+ fixupAttributes(attributes);
+ removeAttributes(attributes);
+
+ }
+ finally {
+ unsetProperty(CURRENT_PACKAGE);
+ }
+ }
+ }
+
+ /**
+ * Fixup Attributes
+ *
+ * Execute any macros on an export and
+ */
+
+ void fixupAttributes(Attrs attributes) {
+ // Convert any attribute values that have macros.
+ for (String key : attributes.keySet()) {
+ String value = attributes.get(key);
+ if (value.indexOf('$') >= 0) {
+ value = getReplacer().process(value);
+ attributes.put(key, value);
+ }
+ }
+
+ }
+
+ /**
+ * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. You
+ * can add a remove-attribute: directive with a regular expression for
+ * attributes that need to be removed. We also remove all attributes that
+ * have a value of !. This allows you to use macros with ${if} to remove
+ * values.
+ */
+
+ void removeAttributes(Attrs attributes) {
+ String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
+
+ if (remove != null) {
+ Instructions removeInstr = new Instructions(remove);
+ attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
+ }
+
+ // Remove any ! valued attributes
+ for (Iterator<Entry<String, String>> i = attributes.entrySet().iterator(); i.hasNext();) {
+ String v = i.next().getValue();
+ if (v.equals("!"))
+ i.remove();
+ }
+ }
+
+ /**
+ * Calculate a version from a version policy.
+ *
+ * @param version The actual exported version
+ * @param impl true for implementations and false for clients
+ */
+
+ String calculateVersionRange(String version, boolean impl) {
+ setProperty("@", version);
+ try {
+ return getVersionPolicy(impl);
+ }
+ finally {
+ unsetProperty("@");
+ }
+ }
+
+ /**
+ * Add the uses clauses. This method iterates over the exports and cal
+ *
+ * @param exports
+ * @param uses
+ * @throws MojoExecutionException
+ */
+ void doUses(Packages exports, MultiMap<PackageRef, PackageRef> uses, Packages imports) {
+ if ("true".equalsIgnoreCase(getProperty(NOUSES)))
+ return;
+
+ for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
+ PackageRef packageRef = i.next();
+ String packageName = packageRef.getFQN();
+ setProperty(CURRENT_PACKAGE, packageName);
+ try {
+ doUses(packageRef, exports, uses, imports);
+ }
+ finally {
+ unsetProperty(CURRENT_PACKAGE);
+ }
+
+ }
+ }
+
+ /**
+ * @param packageName
+ * @param exports
+ * @param uses
+ * @param imports
+ */
+ protected void doUses(PackageRef packageRef, Packages exports,
+ MultiMap<PackageRef, PackageRef> uses, Packages imports) {
+ Attrs clause = exports.get(packageRef);
+
+ // Check if someone already set the uses: directive
+ String override = clause.get(USES_DIRECTIVE);
+ if (override == null)
+ override = USES_USES;
+
+ // Get the used packages
+ Collection<PackageRef> usedPackages = uses.get(packageRef);
+
+ if (usedPackages != null) {
+
+ // Only do a uses on exported or imported packages
+ // and uses should also not contain our own package
+ // name
+ Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
+ sharedPackages.addAll(imports.keySet());
+ sharedPackages.addAll(exports.keySet());
+ sharedPackages.retainAll(usedPackages);
+ sharedPackages.remove(packageRef);
+
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
+ PackageRef usedPackage = u.next();
+ if (!usedPackage.isJava()) {
+ sb.append(del);
+ sb.append(usedPackage.getFQN());
+ del = ",";
+ }
+ }
+ if (override.indexOf('$') >= 0) {
+ setProperty(CURRENT_USES, sb.toString());
+ override = getReplacer().process(override);
+ unsetProperty(CURRENT_USES);
+ }
+ else
+ // This is for backward compatibility 0.0.287
+ // can be deprecated over time
+ override = override.replaceAll(USES_USES, Matcher.quoteReplacement(sb.toString()))
+ .trim();
+
+ if (override.endsWith(","))
+ override = override.substring(0, override.length() - 1);
+ if (override.startsWith(","))
+ override = override.substring(1);
+ if (override.length() > 0) {
+ clause.put(USES_DIRECTIVE, override);
+ }
+ }
+ }
+
+ /**
+ * Transitively remove all elemens from unreachable through the uses link.
+ *
+ * @param name
+ * @param unreachable
+ */
+ void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
+ if (!unreachable.contains(name))
+ return;
+
+ unreachable.remove(name);
+
+ List<PackageRef> ref = uses.get(name);
+ if (ref != null) {
+ for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
+ PackageRef element = r.next();
+ removeTransitive(element, unreachable);
+ }
+ }
+ }
+
+ /**
+ * Helper method to set the package info resource
+ *
+ * @param dir
+ * @param key
+ * @param value
+ * @throws Exception
+ */
+ void setPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
+ throws Exception {
+ if (r == null)
+ return;
+
+ Properties p = new Properties();
+ InputStream in = r.openInputStream();
+ try {
+ p.load(in);
+ }
+ finally {
+ in.close();
+ }
+ Attrs map = classpathExports.get(packageRef);
+ if (map == null) {
+ classpathExports.put(packageRef, map = new Attrs());
+ }
+ for (@SuppressWarnings("unchecked")
+ Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
+ String key = t.nextElement();
+ String value = map.get(key);
+ if (value == null) {
+ value = p.getProperty(key);
+
+ // Messy, to allow directives we need to
+ // allow the value to start with a ':' since we cannot
+ // encode this in a property name
+
+ if (value.startsWith(":")) {
+ key = key + ":";
+ value = value.substring(1);
+ }
+ map.put(key, value);
+ }
+ }
+ }
+
+ public void close() {
+ if (diagnostics) {
+ PrintStream out = System.err;
+ out.printf("Current directory : %s%n", new File("").getAbsolutePath());
+ out.println("Classpath used");
+ for (Jar jar : getClasspath()) {
+ out.printf("File : %s%n", jar.getSource());
+ out.printf("File abs path : %s%n", jar.getSource()
+ .getAbsolutePath());
+ out.printf("Name : %s%n", jar.getName());
+ Map<String, Map<String, Resource>> dirs = jar.getDirectories();
+ for (Map.Entry<String, Map<String, Resource>> entry : dirs.entrySet()) {
+ Map<String, Resource> dir = entry.getValue();
+ String name = entry.getKey().replace('/', '.');
+ if (dir != null) {
+ out.printf(" %-30s %d%n", name,
+ dir.size());
+ }
+ else {
+ out.printf(" %-30s <<empty>>%n", name);
+ }
+ }
+ }
+ }
+
+ super.close();
+ if (dot != null)
+ dot.close();
+
+ if (classpath != null)
+ for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
+ Jar jar = j.next();
+ jar.close();
+ }
+ }
+
+ /**
+ * Findpath looks through the contents of the JAR and finds paths that end
+ * with the given regular expression
+ *
+ * ${findpath (; reg-expr (; replacement)? )? }
+ *
+ * @param args
+ * @return
+ */
+ public String _findpath(String args[]) {
+ return findPath("findpath", args, true);
+ }
+
+ public String _findname(String args[]) {
+ return findPath("findname", args, false);
+ }
+
+ String findPath(String name, String[] args, boolean fullPathName) {
+ if (args.length > 3) {
+ warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args)
+ + ", syntax: ${" + name + " (; reg-expr (; replacement)? )? }");
+ return null;
+ }
+
+ String regexp = ".*";
+ String replace = null;
+
+ switch (args.length) {
+ case 3 :
+ replace = args[2];
+ //$FALL-THROUGH$
+ case 2 :
+ regexp = args[1];
+ }
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+
+ Pattern expr = Pattern.compile(regexp);
+ for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
+ String path = e.next();
+ if (!fullPathName) {
+ int n = path.lastIndexOf('/');
+ if (n >= 0) {
+ path = path.substring(n + 1);
+ }
+ }
+
+ Matcher m = expr.matcher(path);
+ if (m.matches()) {
+ if (replace != null)
+ path = m.replaceAll(replace);
+
+ sb.append(del);
+ sb.append(path);
+ del = ", ";
+ }
+ }
+ return sb.toString();
+ }
+
+ public void putAll(Map<String, String> additional, boolean force) {
+ for (Iterator<Map.Entry<String, String>> i = additional.entrySet().iterator(); i.hasNext();) {
+ Map.Entry<String, String> entry = i.next();
+ if (force || getProperties().get(entry.getKey()) == null)
+ setProperty(entry.getKey(), entry.getValue());
+ }
+ }
+
+ boolean firstUse = true;
+
+ public List<Jar> getClasspath() {
+ if (firstUse) {
+ firstUse = false;
+ String cp = getProperty(CLASSPATH);
+ if (cp != null)
+ for (String s : split(cp)) {
+ Jar jar = getJarFromName(s, "getting classpath");
+ if (jar != null)
+ addClasspath(jar);
+ else
+ warning("Cannot find entry on -classpath: %s", s);
+ }
+ }
+ return classpath;
+ }
+
+ public void addClasspath(Jar jar) {
+ if (isPedantic() && jar.getResources().isEmpty())
+ warning("There is an empty jar or directory on the classpath: " + jar.getName());
+
+ classpath.add(jar);
+ }
+
+ public void addClasspath(Collection< ? > jars) throws IOException {
+ for (Object jar : jars) {
+ if (jar instanceof Jar)
+ addClasspath((Jar) jar);
+ else
+ if (jar instanceof File)
+ addClasspath((File) jar);
+ else
+ if (jar instanceof String)
+ addClasspath(getFile((String) jar));
+ else
+ error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String",
+ jar);
+ }
+ }
+
+ public void addClasspath(File cp) throws IOException {
+ if (!cp.exists())
+ warning("File on classpath that does not exist: " + cp);
+ Jar jar = new Jar(cp);
+ addClose(jar);
+ classpath.add(jar);
+ }
+
+ public void clear() {
+ classpath.clear();
+ }
+
+ public Jar getTarget() {
+ return dot;
+ }
+
+ private void analyzeBundleClasspath() throws Exception {
+ Parameters bcp = getBundleClasspath();
+
+ if (bcp.isEmpty()) {
+ analyzeJar(dot, "", true);
+ }
+ else {
+ boolean okToIncludeDirs = true;
+
+ for (String path : bcp.keySet()) {
+ if (dot.getDirectories().containsKey(path)) {
+ okToIncludeDirs = false;
+ break;
+ }
+ }
+
+ for (String path : bcp.keySet()) {
+ Attrs info = bcp.get(path);
+
+ if (path.equals(".")) {
+ analyzeJar(dot, "", okToIncludeDirs);
+ continue;
+ }
+ //
+ // There are 3 cases:
+ // - embedded JAR file
+ // - directory
+ // - error
+ //
+
+ Resource resource = dot.getResource(path);
+ if (resource != null) {
+ try {
+ Jar jar = new Jar(path);
+ addClose(jar);
+ EmbeddedResource.build(jar, resource);
+ analyzeJar(jar, "", true);
+ }
+ catch (Exception e) {
+ warning("Invalid bundle classpath entry: " + path + " " + e);
+ }
+ }
+ else {
+ if (dot.getDirectories().containsKey(path)) {
+ // if directories are used, we should not have dot as we
+ // would have the classes in these directories on the
+ // class path twice.
+ if (bcp.containsKey("."))
+ warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
+ path, path);
+ analyzeJar(dot, Processor.appendPath(path) + "/", true);
+ }
+ else {
+ if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
+ warning("No sub JAR or directory " + path);
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * We traverse through all the classes that we can find and calculate the
+ * contained and referred set and uses. This method ignores the Bundle
+ * classpath.
+ *
+ * @param jar
+ * @param contained
+ * @param referred
+ * @param uses
+ * @throws IOException
+ */
+ private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
+ Map<String, Clazz> mismatched = new HashMap<String, Clazz>();
+
+ next: for (String path : jar.getResources().keySet()) {
+ if (path.startsWith(prefix)) {
+
+ String relativePath = path.substring(prefix.length());
+
+ if (okToIncludeDirs) {
+ int n = relativePath.lastIndexOf('/');
+ if (n < 0)
+ n = relativePath.length();
+ String relativeDir = relativePath.substring(0, n);
+
+ PackageRef packageRef = getPackageRef(relativeDir);
+ if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
+ contained.put(packageRef);
+
+ // For each package we encounter for the first
+ // time. Unfortunately we can only do this once
+ // we found a class since the bcp has a tendency
+ // to overlap
+ if (!packageRef.isMetaData()) {
+ Resource pinfo = jar.getResource(prefix + packageRef.getPath()
+ + "/packageinfo");
+ setPackageInfo(packageRef, pinfo, classpathExports);
+ }
+ }
+ }
+
+ // Check class resources, we need to analyze them
+ if (path.endsWith(".class")) {
+ Resource resource = jar.getResource(path);
+ Clazz clazz;
+ Attrs info = null;
+
+ try {
+ InputStream in = resource.openInputStream();
+ clazz = new Clazz(this, path, resource);
+ try {
+ // Check if we have a package-info
+ if (relativePath.endsWith("/package-info.class")) {
+ // package-info can contain an Export annotation
+ info = new Attrs();
+ parsePackageInfoClass(clazz, info);
+ }
+ else {
+ // Otherwise we just parse it simply
+ clazz.parseClassFile();
+ }
+ }
+ finally {
+ in.close();
+ }
+ }
+ catch (Throwable e) {
+ error("Invalid class file %s (%s)", e, relativePath, e);
+ e.printStackTrace();
+ continue next;
+ }
+
+ String calculatedPath = clazz.getClassName().getPath();
+ if (!calculatedPath.equals(relativePath)) {
+ // If there is a mismatch we
+ // warning
+ if (okToIncludeDirs) // assume already reported
+ mismatched.put(clazz.getAbsolutePath(), clazz);
+ }
+ else {
+ classspace.put(clazz.getClassName(), clazz);
+ PackageRef packageRef = clazz.getClassName().getPackageRef();
+
+ if (!contained.containsKey(packageRef)) {
+ contained.put(packageRef);
+ if (!packageRef.isMetaData()) {
+ Resource pinfo = jar.getResource(prefix + packageRef.getPath()
+ + "/packageinfo");
+ setPackageInfo(packageRef, pinfo, classpathExports);
+ }
+ }
+ if (info != null)
+ contained.merge(packageRef, false, info);
+
+ Set<PackageRef> set = Create.set();
+
+ // Look at the referred packages
+ // and copy them to our baseline
+ for (PackageRef p : clazz.getReferred()) {
+ referred.put(p);
+ set.add(p);
+ }
+ set.remove(packageRef);
+ uses.addAll(packageRef, set);
+ }
+ }
+ }
+ }
+
+ if (mismatched.size() > 0) {
+ error("Classes found in the wrong directory: %s", mismatched);
+ return false;
+ }
+ return true;
+ }
+
+ static Pattern OBJECT_REFERENCE = Pattern.compile("L([^/]+/)*([^;]+);");
+
+ private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
+ clazz.parseClassFileWithCollector(new ClassDataCollector() {
+ @Override
+ public void annotation(Annotation a) {
+ String name = a.name.getFQN();
+ if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
+
+ // Check version
+ String version = a.get("value");
+ if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
+ if (version != null) {
+ version = getReplacer().process(version);
+ if (Verifier.VERSION.matcher(version).matches())
+ info.put(VERSION_ATTRIBUTE, version);
+ else
+ error("Export annotation in %s has invalid version info: %s",
+ clazz, version);
+ }
+ }
+ else {
+ // Verify this matches with packageinfo
+ String presentVersion = info.get(VERSION_ATTRIBUTE);
+ try {
+ Version av = new Version(presentVersion);
+ Version bv = new Version(version);
+ if (!av.equals(bv)) {
+ error("Version from annotation for %s differs with packageinfo or Manifest",
+ clazz.getClassName().getFQN());
+ }
+ }
+ catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+ else
+ if (name.equals(Export.class.getName())) {
+
+ // Check mandatory attributes
+ Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz,
+ getReplacer());
+ if (!attrs.isEmpty()) {
+ info.putAll(attrs);
+ info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
+ }
+
+ // Check optional attributes
+ attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
+ if (!attrs.isEmpty()) {
+ info.putAll(attrs);
+ }
+
+ // Check Included classes
+ Object[] included = a.get(Export.INCLUDE);
+ if (included != null && included.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (Object i : included) {
+ Matcher m = OBJECT_REFERENCE.matcher((String) i);
+ if (m.matches()) {
+ sb.append(del);
+ sb.append(m.group(2));
+ del = ",";
+ }
+ }
+ info.put(INCLUDE_DIRECTIVE, sb.toString());
+ }
+
+ // Check Excluded classes
+ Object[] excluded = a.get(Export.EXCLUDE);
+ if (excluded != null && excluded.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (Object i : excluded) {
+ Matcher m = OBJECT_REFERENCE.matcher((String) i);
+ if (m.matches()) {
+ sb.append(del);
+ sb.append(m.group(2));
+ del = ",";
+ }
+ }
+ info.put(EXCLUDE_DIRECTIVE, sb.toString());
+ }
+
+ // Check Uses
+ Object[] uses = a.get(Export.USES);
+ if (uses != null && uses.length > 0) {
+ String old = info.get(USES_DIRECTIVE);
+ if (old == null)
+ old = "";
+ StringBuilder sb = new StringBuilder(old);
+ String del = sb.length() == 0 ? "" : ",";
+
+ for (Object use : uses) {
+ sb.append(del);
+ sb.append(use);
+ del = ",";
+ }
+ info.put(USES_DIRECTIVE, sb.toString());
+ }
+ }
+ }
+
+ });
+ }
+
+ /**
+ * Clean up version parameters. Other builders use more fuzzy definitions of
+ * the version syntax. This method cleans up such a version to match an OSGi
+ * version.
+ *
+ * @param VERSION_STRING
+ * @return
+ */
+ static Pattern fuzzyVersion = Pattern
+ .compile(
+ "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
+ Pattern.DOTALL);
+ static Pattern fuzzyVersionRange = Pattern
+ .compile(
+ "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
+ Pattern.DOTALL);
+ static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)", Pattern.DOTALL);
+
+ static Pattern nummeric = Pattern.compile("\\d*");
+
+ static public String cleanupVersion(String version) {
+ Matcher m = Verifier.VERSIONRANGE.matcher(version);
+
+ if (m.matches()) {
+ return version;
+ }
+
+ m = fuzzyVersionRange.matcher(version);
+ if (m.matches()) {
+ String prefix = m.group(1);
+ String first = m.group(2);
+ String last = m.group(3);
+ String suffix = m.group(4);
+ return prefix + cleanupVersion(first) + "," + cleanupVersion(last) + suffix;
+ }
+ else {
+ m = fuzzyVersion.matcher(version);
+ if (m.matches()) {
+ StringBuilder result = new StringBuilder();
+ String major = removeLeadingZeroes(m.group(1));
+ String minor = removeLeadingZeroes(m.group(3));
+ String micro = removeLeadingZeroes(m.group(5));
+ String qualifier = m.group(7);
+
+ if (major != null) {
+ result.append(major);
+ if (minor != null) {
+ result.append(".");
+ result.append(minor);
+ if (micro != null) {
+ result.append(".");
+ result.append(micro);
+ if (qualifier != null) {
+ result.append(".");
+ cleanupModifier(result, qualifier);
+ }
+ }
+ else
+ if (qualifier != null) {
+ result.append(".0.");
+ cleanupModifier(result, qualifier);
+ }
+ }
+ else
+ if (qualifier != null) {
+ result.append(".0.0.");
+ cleanupModifier(result, qualifier);
+ }
+ return result.toString();
+ }
+ }
+ }
+ return version;
+ }
+
+ private static String removeLeadingZeroes(String group) {
+ int n = 0;
+ while (group != null && n < group.length() - 1 && group.charAt(n) == '0')
+ n++;
+ if (n == 0)
+ return group;
+
+ return group.substring(n);
+ }
+
+ static void cleanupModifier(StringBuilder result, String modifier) {
+ Matcher m = fuzzyModifier.matcher(modifier);
+ if (m.matches())
+ modifier = m.group(2);
+
+ for (int i = 0; i < modifier.length(); i++) {
+ char c = modifier.charAt(i);
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || c == '_' || c == '-')
+ result.append(c);
+ }
+ }
+
+ final static String DEFAULT_PROVIDER_POLICY = "${range;[==,=+)}";
+ final static String DEFAULT_CONSUMER_POLICY = "${range;[==,+)}";
+
+ @SuppressWarnings("deprecation")
+ public String getVersionPolicy(boolean implemented) {
+ if (implemented) {
+ String s = getProperty(PROVIDER_POLICY);
+ if (s != null)
+ return s;
+
+ s = getProperty(VERSIONPOLICY_IMPL);
+ if (s != null)
+ return s;
+
+ return getProperty(VERSIONPOLICY, DEFAULT_PROVIDER_POLICY);
+ }
+ else {
+ String s = getProperty(CONSUMER_POLICY);
+ if (s != null)
+ return s;
+
+ s = getProperty(VERSIONPOLICY_USES);
+ if (s != null)
+ return s;
+
+ return getProperty(VERSIONPOLICY, DEFAULT_CONSUMER_POLICY);
+ }
+ // String vp = implemented ? getProperty(VERSIONPOLICY_IMPL) :
+ // getProperty(VERSIONPOLICY_USES);
+ //
+ // if (vp != null)
+ // return vp;
+ //
+ // if (implemented)
+ // return getProperty(VERSIONPOLICY_IMPL, "{$range;[==,=+}");
+ // else
+ // return getProperty(VERSIONPOLICY, "${range;[==,+)}");
+ }
+
+ /**
+ * The extends macro traverses all classes and returns a list of class names
+ * that extend a base class.
+ */
+
+ static String _classesHelp = "${classes;'implementing'|'extending'|'importing'|'named'|'version'|'any';<pattern>}, Return a list of class fully qualified class names that extend/implement/import any of the contained classes matching the pattern\n";
+
+ public String _classes(String... args) throws Exception {
+ // Macro.verifyCommand(args, _classesHelp, new
+ // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
+ // null}, 3,3);
+
+ Collection<Clazz> matched = getClasses(args);
+ if (matched.isEmpty())
+ return "";
+
+ return join(matched);
+ }
+
+ public Collection<Clazz> getClasses(String... args) throws Exception {
+
+ Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
+ for (int i = 1; i < args.length; i++) {
+ if (args.length < i + 1)
+ throw new IllegalArgumentException(
+ "${classes} macro must have odd number of arguments. " + _classesHelp);
+
+ String typeName = args[i];
+ if (typeName.equalsIgnoreCase("extending"))
+ typeName = "extends";
+ else
+ if (typeName.equalsIgnoreCase("importing"))
+ typeName = "imports";
+ else
+ if (typeName.equalsIgnoreCase("implementing"))
+ typeName = "implements";
+
+ Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
+
+ if (type == null)
+ throw new IllegalArgumentException("${classes} has invalid type: " + typeName
+ + ". " + _classesHelp);
+
+ Instruction instr = null;
+ if (Clazz.HAS_ARGUMENT.contains(type)) {
+ String s = args[++i];
+ instr = new Instruction(s);
+ }
+ for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
+ Clazz clazz = c.next();
+ if (!clazz.is(type, instr, this)) {
+ c.remove();
+ }
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Get the exporter of a package ...
+ */
+
+ public String _exporters(String args[]) throws Exception {
+ Macro.verifyCommand(
+ args,
+ "${exporters;<packagename>}, returns the list of jars that export the given package",
+ null, 2, 2);
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ String pack = args[1].replace('.', '/');
+ for (Jar jar : classpath) {
+ if (jar.getDirectories().containsKey(pack)) {
+ sb.append(del);
+ sb.append(jar.getName());
+ }
+ }
+ return sb.toString();
+ }
+
+ public Map<TypeRef, Clazz> getClassspace() {
+ return classspace;
+ }
+
+ /**
+ * Locate a resource on the class path.
+ *
+ * @param path Path of the reosurce
+ * @return A resource or <code>null</code>
+ */
+ public Resource findResource(String path) {
+ for (Jar entry : getClasspath()) {
+ Resource r = entry.getResource(path);
+ if (r != null)
+ return r;
+ }
+ return null;
+ }
+
+ /**
+ * Find a clazz on the class path. This class has been parsed.
+ *
+ * @param path
+ * @return
+ */
+ public Clazz findClass(TypeRef typeRef) throws Exception {
+ Clazz c = classspace.get(typeRef);
+ if (c != null)
+ return c;
+
+ c = importedClassesCache.get(typeRef);
+ if (c != null)
+ return c;
+
+ Resource r = findResource(typeRef.getPath());
+ if (r == null) {
+ getClass().getClassLoader();
+ URL url = ClassLoader.getSystemResource(typeRef.getPath());
+ if (url != null)
+ r = new URLResource(url);
+ }
+ if (r != null) {
+ c = new Clazz(this, typeRef.getPath(), r);
+ c.parseClassFile();
+ importedClassesCache.put(typeRef, c);
+ }
+ return c;
+ }
+
+ /**
+ * Answer the bundle version.
+ *
+ * @return
+ */
+ public String getVersion() {
+ String version = getProperty(BUNDLE_VERSION);
+ if (version == null)
+ version = "0.0.0";
+ return version;
+ }
+
+ public boolean isNoBundle() {
+ return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
+ }
+
+ public void referTo(TypeRef ref) {
+ PackageRef pack = ref.getPackageRef();
+ if (!referred.containsKey(pack))
+ referred.put(pack, new Attrs());
+ }
+
+ public void referToByBinaryName(String binaryClassName) {
+ TypeRef ref = descriptors.getTypeRef(binaryClassName);
+ referTo(ref);
+ }
+
+ /**
+ * Ensure that we are running on the correct bnd.
+ */
+ void doRequireBnd() {
+ Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
+ if (require == null || require.isEmpty())
+ return;
+
+ Hashtable<String, String> map = new Hashtable<String, String>();
+ map.put(Constants.VERSION_FILTER, getBndVersion());
+
+ for (String filter : require.keySet()) {
+ try {
+ Filter f = new Filter(filter);
+ if (f.match(map))
+ continue;
+ error("%s fails %s", REQUIRE_BND, require.get(filter));
+ }
+ catch (Exception t) {
+ error("%s with value %s throws exception", t, REQUIRE_BND, require);
+ }
+ }
+ }
+
+ /**
+ * md5 macro
+ */
+
+ static String _md5Help = "${md5;path}";
+
+ public String _md5(String args[]) throws Exception {
+ Macro.verifyCommand(args, _md5Help,
+ new Pattern[] {null, null, Pattern.compile("base64|hex")}, 2, 3);
+
+ Digester<MD5> digester = MD5.getDigester();
+ Resource r = dot.getResource(args[1]);
+ if (r == null)
+ throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
+
+ IO.copy(r.openInputStream(), digester);
+ boolean hex = args.length > 2 && args[2].equals("hex");
+ if (hex)
+ return Hex.toHexString(digester.digest().digest());
+ else
+ return Base64.encodeBase64(digester.digest().digest());
+ }
+
+ /**
+ * SHA1 macro
+ */
+
+ static String _sha1Help = "${sha1;path}";
+
+ public String _sha1(String args[]) throws Exception {
+ Macro.verifyCommand(args, _sha1Help,
+ new Pattern[] {null, null, Pattern.compile("base64|hex")}, 2, 3);
+ Digester<SHA1> digester = SHA1.getDigester();
+ Resource r = dot.getResource(args[1]);
+ if (r == null)
+ throw new FileNotFoundException("From sha1, not found " + args[1]);
+
+ IO.copy(r.openInputStream(), digester);
+ return Base64.encodeBase64(digester.digest().digest());
+ }
+
+ public Descriptor getDescriptor(String descriptor) {
+ return descriptors.getDescriptor(descriptor);
+ }
+
+ public TypeRef getTypeRef(String binaryClassName) {
+ return descriptors.getTypeRef(binaryClassName);
+ }
+
+ public PackageRef getPackageRef(String binaryName) {
+ return descriptors.getPackageRef(binaryName);
+ }
+
+ public TypeRef getTypeRefFromFQN(String fqn) {
+ return descriptors.getTypeRefFromFQN(fqn);
+ }
+
+ public TypeRef getTypeRefFromPath(String path) {
+ return descriptors.getTypeRefFromPath(path);
+ }
+
+ public boolean isImported(PackageRef packageRef) {
+ return imports.containsKey(packageRef);
+ }
+
+ /**
+ * Merge the attributes of two maps, where the first map can contain
+ * wildcarded names. The idea is that the first map contains instructions
+ * (for example *) with a set of attributes. These patterns are matched
+ * against the found packages in actual. If they match, the result is set
+ * with the merged set of attributes. It is expected that the instructions
+ * are ordered so that the instructor can define which pattern matches
+ * first. Attributes in the instructions override any attributes from the
+ * actual.<br/>
+ *
+ * A pattern is a modified regexp so it looks like globbing. The * becomes a
+ * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
+ * if the pattern starts with an exclamation mark, it will remove that
+ * matches for that pattern (- the !) from the working set. So the following
+ * patterns should work:
+ * <ul>
+ * <li>com.foo.bar</li>
+ * <li>com.foo.*</li>
+ * <li>com.foo.???</li>
+ * <li>com.*.[^b][^a][^r]</li>
+ * <li>!com.foo.* (throws away any match for com.foo.*)</li>
+ * </ul>
+ * Enough rope to hang the average developer I would say.
+ *
+ *
+ * @param instructions the instructions with patterns.
+ * @param source the actual found packages, contains no duplicates
+ *
+ * @return Only the packages that were filtered by the given instructions
+ */
+
+ Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
+ Packages result = new Packages();
+ List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
+ Collections.sort(refs);
+
+ List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
+ if (nomatch == null)
+ nomatch = Create.set();
+
+ for (Instruction instruction : filters) {
+ boolean match = false;
+
+ for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
+ PackageRef packageRef = i.next();
+
+ if (packageRef.isMetaData()) {
+ i.remove(); // no use checking it again
+ continue;
+ }
+
+ String packageName = packageRef.getFQN();
+
+ if (instruction.matches(packageName)) {
+ match = true;
+ if (!instruction.isNegated()) {
+ result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
+ instructions.get(instruction));
+ }
+ i.remove(); // Can never match again for another pattern
+ }
+ }
+ if (!match && !instruction.isAny())
+ nomatch.add(instruction);
+ }
+
+ /*
+ * Tricky. If we have umatched instructions they might indicate that we
+ * want to have multiple decorators for the same package. So we check
+ * the unmatched against the result list. If then then match and have
+ * actually interesting properties then we merge them
+ */
+
+ for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
+ Instruction instruction = i.next();
+
+ // We assume the user knows what he is
+ // doing and inserted a literal. So
+ // we ignore any not matched literals
+ if (instruction.isLiteral()) {
+ result.merge(getPackageRef(instruction.getLiteral()), true,
+ instructions.get(instruction));
+ i.remove();
+ continue;
+ }
+
+ // Not matching a negated instruction looks
+ // like an error ...
+ if (instruction.isNegated()) {
+ continue;
+ }
+
+ // An optional instruction should not generate
+ // an error
+ if (instruction.isOptional()) {
+ i.remove();
+ continue;
+ }
+
+ // boolean matched = false;
+ // Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
+ // for (PackageRef ref : prefs) {
+ // if (instruction.matches(ref.getFQN())) {
+ // result.merge(ref, true, source.get(ref),
+ // instructions.get(instruction));
+ // matched = true;
+ // }
+ // }
+ // if (matched)
+ // i.remove();
+ }
+ return result;
+ }
+
+ public void setDiagnostics(boolean b) {
+ diagnostics = b;
+ }
+
+ public Clazz.JAVA getLowestEE() {
+ if (ees.isEmpty())
+ return Clazz.JAVA.JDK1_4;
+
+ return ees.first();
+ }
+
+ public String _ee(String args[]) {
+ return getLowestEE().getEE();
+ }
+
+ /**
+ * Calculate the output file for the given target. The strategy is:
+ *
+ * <pre>
+ * parameter given if not null and not directory
+ * if directory, this will be the output directory
+ * based on bsn-version.jar
+ * name of the source file if exists
+ * Untitled-[n]
+ * </pre>
+ *
+ * @param output may be null, otherwise a file path relative to base
+ */
+ public File getOutputFile(String output) {
+
+ if (output == null)
+ output = get(Constants.OUTPUT);
+
+ File outputDir;
+
+ if (output != null) {
+ File outputFile = getFile(output);
+ if (outputFile.isDirectory())
+ outputDir = outputFile;
+ else
+ return outputFile;
+ }
+ else
+ outputDir = getBase();
+
+ if (getBundleSymbolicName() != null) {
+ String bsn = getBundleSymbolicName();
+ String version = getBundleVersion();
+ Version v = Version.parseVersion(version);
+ String outputName = bsn + "-" + v.getWithoutQualifier()
+ + Constants.DEFAULT_JAR_EXTENSION;
+ return new File(outputDir, outputName);
+ }
+
+ File source = getJar().getSource();
+ if (source != null) {
+ String outputName = source.getName();
+ return new File(outputDir, outputName);
+ }
+
+ error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled",
+ output);
+ int n = 0;
+ File f = getFile(outputDir, "Untitled");
+ while (f.isFile()) {
+ f = getFile(outputDir, "Untitled-" + n++);
+ }
+ return f;
+ }
+
+ /**
+ * Utility function to carefully save the file. Will create a backup if the
+ * source file has the same path as the output. It will also only save if
+ * the file was modified or the force flag is true
+ *
+ * @param output the output file, if null {@link #getOutputFile(String)} is
+ * used.
+ * @param force if it needs to be overwritten
+ * @throws Exception
+ */
+
+ public boolean save(File output, boolean force) throws Exception {
+ if (output == null)
+ output = getOutputFile(null);
+
+ Jar jar = getJar();
+ File source = jar.getSource();
+
+ trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(),
+ output.lastModified(), jar.lastModified() - output.lastModified());
+
+ if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
+ output.getParentFile().mkdirs();
+ if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
+ File bak = new File(source.getParentFile(), source.getName() + ".bak");
+ if (!source.renameTo(bak)) {
+ error("Could not create backup file %s", bak);
+ }
+ else
+ source.delete();
+ }
+ try {
+ trace("Saving jar to %s", output);
+ getJar().write(output);
+ }
+ catch (Exception e) {
+ output.delete();
+ error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
+ }
+ return true;
+ }
+ else {
+ trace("Not modified %s", output);
+ return false;
+ }
+ }
+
+ /**
+ * Set default import and export instructions if none are set
+ */
+ public void setDefaults(String bsn, Version version) {
+ if (getExportPackage() == null)
+ setExportPackage("*");
+ if (getImportPackage() == null)
+ setExportPackage("*");
+ if (bsn != null && getBundleSymbolicName() == null)
+ setBundleSymbolicName(bsn);
+ if (version != null && getBundleVersion() == null)
+ setBundleVersion(version);
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Annotation.java b/bundleplugin/src/main/java/aQute/lib/osgi/Annotation.java
new file mode 100644
index 0000000..86f8caa
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Annotation.java
@@ -0,0 +1,74 @@
+package aQute.lib.osgi;
+
+import java.lang.annotation.*;
+import java.util.*;
+
+import aQute.bnd.annotation.metatype.*;
+import aQute.lib.osgi.Descriptors.TypeRef;
+
+@SuppressWarnings("unchecked") public class Annotation {
+ TypeRef name;
+ Map<String, Object> elements;
+ ElementType member;
+ RetentionPolicy policy;
+
+ public Annotation(TypeRef name, Map<String, Object> elements, ElementType member,
+ RetentionPolicy policy) {
+ this.name = name;
+ if ( elements == null)
+ this.elements = Collections.emptyMap();
+ else
+ this.elements = elements;
+ this.member = member;
+ this.policy = policy;
+ }
+
+ public TypeRef getName() {
+ return name;
+ }
+
+ public ElementType getElementType() {
+ return member;
+ }
+
+ public RetentionPolicy getRetentionPolicy() {
+ return policy;
+ }
+
+ public String toString() {
+ return name + ":" + member + ":" + policy + ":" + elements;
+ }
+
+ public <T> T get(String string) {
+ if (elements == null)
+ return null;
+
+ return (T) elements.get(string);
+ }
+
+ public <T> void put(String string, Object v) {
+ if (elements == null)
+ return;
+
+ elements.put(string, v);
+ }
+
+ public Set<String> keySet() {
+ if (elements == null)
+ return Collections.emptySet();
+
+ return elements.keySet();
+ }
+ public <T extends java.lang.annotation.Annotation> T getAnnotation() throws Exception {
+ String cname = name.getFQN();
+ Class<T> c = (Class<T>) getClass().getClassLoader().loadClass(cname);
+ return getAnnotation(c);
+ }
+ public <T extends java.lang.annotation.Annotation> T getAnnotation(Class<T> c)
+ throws Exception {
+ String cname = name.getFQN();
+ if ( ! c.getName().equals(cname))
+ return null;
+ return Configurable.createConfigurable(c, elements );
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
new file mode 100755
index 0000000..3126056
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
@@ -0,0 +1,1558 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.bnd.component.*;
+import aQute.bnd.differ.*;
+import aQute.bnd.differ.Baseline.Info;
+import aQute.bnd.make.*;
+import aQute.bnd.make.component.*;
+import aQute.bnd.make.metatype.*;
+import aQute.bnd.maven.*;
+import aQute.bnd.service.*;
+import aQute.bnd.service.diff.*;
+import aQute.lib.collections.*;
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+
+/**
+ * Include-Resource: ( [name '=' ] file )+
+ *
+ * Private-Package: package-decl ( ',' package-decl )*
+ *
+ * Export-Package: package-decl ( ',' package-decl )*
+ *
+ * Import-Package: package-decl ( ',' package-decl )*
+ *
+ * @version $Revision$
+ */
+public class Builder extends Analyzer {
+ static Pattern IR_PATTERN = Pattern
+ .compile("[{]?-?@?(?:[^=]+=)?\\s*([^}!]+).*");
+ private final DiffPluginImpl differ = new DiffPluginImpl();
+ private Pattern xdoNotCopy = null;
+ private static final int SPLIT_MERGE_LAST = 1;
+ private static final int SPLIT_MERGE_FIRST = 2;
+ private static final int SPLIT_ERROR = 3;
+ private static final int SPLIT_FIRST = 4;
+ private static final int SPLIT_DEFAULT = 0;
+ private final List<File> sourcePath = new ArrayList<File>();
+ private final Make make = new Make(this);
+
+ public Builder(Processor parent) {
+ super(parent);
+ }
+
+ public Builder() {
+ }
+
+ public Jar build() throws Exception {
+ trace("build");
+ init();
+ if (isTrue(getProperty(NOBUNDLES)))
+ return null;
+
+ if (getProperty(CONDUIT) != null)
+ error("Specified " + CONDUIT
+ + " but calls build() instead of builds() (might be a programmer error");
+
+ Jar dot = new Jar("dot");
+ try {
+ long modified = Long.parseLong(getProperty("base.modified"));
+ dot.updateModified(modified, "Base modified");
+ }
+ catch (Exception e) {
+ // Ignore
+ }
+ setJar(dot);
+
+ doExpand(dot);
+ doIncludeResources(dot);
+ doWab(dot);
+
+ // Check if we override the calculation of the
+ // manifest. We still need to calculated it because
+ // we need to have analyzed the classpath.
+
+ Manifest manifest = calcManifest();
+
+ String mf = getProperty(MANIFEST);
+ if (mf != null) {
+ File mff = getFile(mf);
+ if (mff.isFile()) {
+ try {
+ InputStream in = new FileInputStream(mff);
+ manifest = new Manifest(in);
+ in.close();
+ }
+ catch (Exception e) {
+ error(MANIFEST + " while reading manifest file", e);
+ }
+ }
+ else {
+ error(MANIFEST + ", no such file " + mf);
+ }
+ }
+
+ if (getProperty(NOMANIFEST) == null)
+ dot.setManifest(manifest);
+ else
+ dot.setDoNotTouchManifest();
+
+ // This must happen after we analyzed so
+ // we know what it is on the classpath
+ addSources(dot);
+
+ if (getProperty(POM) != null)
+ dot.putResource("pom.xml", new PomResource(dot.getManifest()));
+
+ if (!isNoBundle())
+ doVerify(dot);
+
+ if (dot.getResources().isEmpty())
+ warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
+ getBsn());
+
+ dot.updateModified(lastModified(), "Last Modified Processor");
+ dot.setName(getBsn());
+
+ sign(dot);
+ doDigests(dot);
+ doSaveManifest(dot);
+
+ doDiff(dot); // check if need to diff this bundle
+ doBaseline(dot); // check for a baseline
+ return dot;
+ }
+
+ /**
+ * Check if we need to calculate any checksums.
+ *
+ * @param dot
+ * @throws Exception
+ */
+ private void doDigests(Jar dot) throws Exception {
+ Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
+ if (ps.isEmpty())
+ return;
+ trace("digests %s", ps);
+ String[] digests = ps.keySet().toArray(new String[ps.size()]);
+ dot.calcChecksums(digests);
+ }
+
+ /**
+ * Allow any local initialization by subclasses before we build.
+ */
+ public void init() throws Exception {
+ begin();
+ doRequireBnd();
+
+ // Check if we have sensible setup
+
+ if (getClasspath().size() == 0
+ && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
+ warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
+
+ }
+
+ /**
+ * Turn this normal bundle in a web and add any resources.
+ *
+ * @throws Exception
+ */
+ private Jar doWab(Jar dot) throws Exception {
+ String wab = getProperty(WAB);
+ String wablib = getProperty(WABLIB);
+ if (wab == null && wablib == null)
+ return dot;
+
+ trace("wab %s %s", wab, wablib);
+ setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
+
+ Set<String> paths = new HashSet<String>(dot.getResources().keySet());
+
+ for (String path : paths) {
+ if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
+ trace("wab: moving: %s", path);
+ dot.rename(path, "WEB-INF/classes/" + path);
+ }
+ }
+
+ Parameters clauses = parseHeader(getProperty(WABLIB));
+ for (String key : clauses.keySet()) {
+ File f = getFile(key);
+ addWabLib(dot, f);
+ }
+ doIncludeResource(dot, wab);
+ return dot;
+ }
+
+ /**
+ * Add a wab lib to the jar.
+ *
+ * @param f
+ */
+ private void addWabLib(Jar dot, File f) throws Exception {
+ if (f.exists()) {
+ Jar jar = new Jar(f);
+ jar.setDoNotTouchManifest();
+ addClose(jar);
+ String path = "WEB-INF/lib/" + f.getName();
+ dot.putResource(path, new JarResource(jar));
+ setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
+
+ Manifest m = jar.getManifest();
+ String cp = m.getMainAttributes().getValue("Class-Path");
+ if (cp != null) {
+ Collection<String> parts = split(cp, ",");
+ for (String part : parts) {
+ File sub = getFile(f.getParentFile(), part);
+ if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
+ warning("Invalid Class-Path entry %s in %s, must exist and must reside in same directory",
+ sub, f);
+ }
+ else {
+ addWabLib(dot, sub);
+ }
+ }
+ }
+ }
+ else {
+ error("WAB lib does not exist %s", f);
+ }
+ }
+
+ /**
+ * Get the manifest and write it out separately if -savemanifest is set
+ *
+ * @param dot
+ */
+ private void doSaveManifest(Jar dot) throws Exception {
+ String output = getProperty(SAVEMANIFEST);
+ if (output == null)
+ return;
+
+ File f = getFile(output);
+ if (f.isDirectory()) {
+ f = new File(f, "MANIFEST.MF");
+ }
+ f.delete();
+ f.getParentFile().mkdirs();
+ OutputStream out = new FileOutputStream(f);
+ try {
+ Jar.writeManifest(dot.getManifest(), out);
+ }
+ finally {
+ out.close();
+ }
+ changedFile(f);
+ }
+
+ protected void changedFile(File f) {
+ }
+
+ /**
+ * Sign the jar file.
+ *
+ * -sign : <alias> [ ';' 'password:=' <password> ] [ ';' 'keystore:='
+ * <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
+ *
+ * @return
+ */
+
+ void sign(Jar jar) throws Exception {
+ String signing = getProperty("-sign");
+ if (signing == null)
+ return;
+
+ trace("Signing %s, with %s", getBsn(), signing);
+ List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
+
+ Parameters infos = parseHeader(signing);
+ for (Entry<String, Attrs> entry : infos.entrySet()) {
+ for (SignerPlugin signer : signers) {
+ signer.sign(this, entry.getKey());
+ }
+ }
+ }
+
+ public boolean hasSources() {
+ return isTrue(getProperty(SOURCES));
+ }
+
+ /**
+ * Answer extra packages. In this case we implement conditional package. Any
+ */
+ protected Jar getExtra() throws Exception {
+ Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
+ if (conditionals.isEmpty())
+ return null;
+ Instructions instructions = new Instructions(conditionals);
+
+ Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
+ referred.removeAll(getContained().keySet());
+
+ Jar jar = new Jar("conditional-import");
+ addClose(jar);
+ for (PackageRef pref : referred) {
+ for (Jar cpe : getClasspath()) {
+ Map<String, Resource> map = cpe.getDirectories().get(pref.getPath());
+ if (map != null) {
+ jar.addDirectory(map, false);
+ break;
+ }
+ }
+ }
+ if (jar.getDirectories().size() == 0)
+ return null;
+ return jar;
+ }
+
+ /**
+ * Intercept the call to analyze and cleanup versions after we have analyzed
+ * the setup. We do not want to cleanup if we are going to verify.
+ */
+
+ public void analyze() throws Exception {
+ super.analyze();
+ cleanupVersion(getImports(), null);
+ cleanupVersion(getExports(), getVersion());
+ String version = getProperty(BUNDLE_VERSION);
+ if (version != null) {
+ version = cleanupVersion(version);
+ if (version.endsWith(".SNAPSHOT")) {
+ version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
+ }
+ setProperty(BUNDLE_VERSION, version);
+ }
+ }
+
+ public void cleanupVersion(Packages packages, String defaultVersion) {
+ for (Map.Entry<PackageRef, Attrs> entry : packages.entrySet()) {
+ Attrs attributes = entry.getValue();
+ String v = attributes.get(Constants.VERSION_ATTRIBUTE);
+ if (v == null && defaultVersion != null) {
+ if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
+ v = defaultVersion;
+ if (isPedantic())
+ warning("Used bundle version %s for exported package %s", v, entry.getKey());
+ }
+ else {
+ if (isPedantic())
+ warning("No export version for exported package %s", entry.getKey());
+ }
+ }
+ if (v != null)
+ attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
+ }
+ }
+
+ /**
+ *
+ */
+ private void addSources(Jar dot) {
+ if (!hasSources())
+ return;
+
+ Set<PackageRef> packages = Create.set();
+
+ for (TypeRef typeRef : getClassspace().keySet()) {
+ PackageRef packageRef = typeRef.getPackageRef();
+ String sourcePath = typeRef.getSourcePath();
+ String packagePath = packageRef.getPath();
+
+ boolean found = false;
+ String[] fixed = {"packageinfo", "package.html", "module-info.java",
+ "package-info.java"};
+
+ for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
+ File root = i.next();
+
+ // TODO should use bcp?
+
+ File f = getFile(root, sourcePath);
+ if (f.exists()) {
+ found = true;
+ if (!packages.contains(packageRef)) {
+ packages.add(packageRef);
+ File bdir = getFile(root, packagePath);
+ for (int j = 0; j < fixed.length; j++) {
+ File ff = getFile(bdir, fixed[j]);
+ if (ff.isFile()) {
+ String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
+ dot.putResource(name, new FileResource(ff));
+ }
+ }
+ }
+ if (packageRef.isDefaultPackage())
+ System.err.println("Duh?");
+ dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
+ }
+ }
+ if (!found) {
+ for (Jar jar : getClasspath()) {
+ Resource resource = jar.getResource(sourcePath);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+ }
+ else {
+ resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+ }
+ }
+ }
+ }
+ if (getSourcePath().isEmpty())
+ warning("Including sources but " + SOURCEPATH
+ + " does not contain any source directories ");
+ // TODO copy from the jars where they came from
+ }
+ }
+
+ boolean firstUse = true;
+ private Tree tree;
+
+ public Collection<File> getSourcePath() {
+ if (firstUse) {
+ firstUse = false;
+ String sp = getProperty(SOURCEPATH);
+ if (sp != null) {
+ Parameters map = parseHeader(sp);
+ for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
+ String file = i.next();
+ if (!isDuplicate(file)) {
+ File f = getFile(file);
+ if (!f.isDirectory()) {
+ error("Adding a sourcepath that is not a directory: " + f);
+ }
+ else {
+ sourcePath.add(f);
+ }
+ }
+ }
+ }
+ }
+ return sourcePath;
+ }
+
+ private void doVerify(Jar dot) throws Exception {
+ Verifier verifier = new Verifier(this);
+ // Give the verifier the benefit of our analysis
+ // prevents parsing the files twice
+ verifier.verify();
+ getInfo(verifier);
+ }
+
+ private void doExpand(Jar dot) throws IOException {
+
+ // Build an index of the class path that we can then
+ // use destructively
+ MultiMap<String, Jar> packages = new MultiMap<String, Jar>();
+ for (Jar srce : getClasspath()) {
+ for (Entry<String, Map<String, Resource>> e : srce.getDirectories().entrySet()) {
+ if (e.getValue() != null)
+ packages.add(e.getKey(), srce);
+ }
+ }
+
+ Parameters privatePackages = getPrivatePackage();
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
+ privatePackages.putAll(parseHeader(h));
+ }
+
+ if (!privatePackages.isEmpty()) {
+ Instructions privateFilter = new Instructions(privatePackages);
+ Set<Instruction> unused = doExpand(dot, packages, privateFilter);
+
+ if (!unused.isEmpty()) {
+ warning("Unused Private-Package instructions, no such package(s) on the class path: %s",
+ unused);
+ }
+ }
+
+ Parameters exportedPackage = getExportPackage();
+ if (!exportedPackage.isEmpty()) {
+ Instructions exportedFilter = new Instructions(exportedPackage);
+
+ // We ignore unused instructions for exports, they should show
+ // up as errors during analysis. Otherwise any overlapping
+ // packages with the private packages should show up as
+ // unused
+
+ doExpand(dot, packages, exportedFilter);
+ }
+ }
+
+ /**
+ * Destructively filter the packages from the build up index. This index is
+ * used by the Export Package as well as the Private Package
+ *
+ * @param jar
+ * @param name
+ * @param instructions
+ */
+ private Set<Instruction> doExpand(Jar jar, MultiMap<String, Jar> index, Instructions filter) {
+ Set<Instruction> unused = Create.set();
+
+ for (Entry<Instruction, Attrs> e : filter.entrySet()) {
+ Instruction instruction = e.getKey();
+ if (instruction.isDuplicate())
+ continue;
+
+ Attrs directives = e.getValue();
+
+ // We can optionally filter on the
+ // source of the package. We assume
+ // they all match but this can be overridden
+ // on the instruction
+ Instruction from = new Instruction(directives.get(FROM_DIRECTIVE, "*"));
+
+ boolean used = false;
+
+ for (Iterator<Entry<String, List<Jar>>> entry = index.entrySet().iterator(); entry
+ .hasNext();) {
+ Entry<String, List<Jar>> p = entry.next();
+
+ String directory = p.getKey();
+ PackageRef packageRef = getPackageRef(directory);
+
+ // Skip * and meta data, we're talking packages!
+ if (packageRef.isMetaData() && instruction.isAny())
+ continue;
+
+ if (!instruction.matches(packageRef.getFQN()))
+ continue;
+
+ // Ensure it is never matched again
+ entry.remove();
+
+ // ! effectively removes it from consideration by others (this
+ // includes exports)
+ if (instruction.isNegated())
+ continue;
+
+ // Do the from: directive, filters on the JAR type
+ List<Jar> providers = filterFrom(from, p.getValue());
+ if (providers.isEmpty())
+ continue;
+
+ int splitStrategy = getSplitStrategy(directives.get(SPLIT_PACKAGE_DIRECTIVE));
+ copyPackage(jar, providers, directory, splitStrategy);
+
+ used = true;
+ }
+
+ if (!used && !isTrue(directives.get("optional:")))
+ unused.add(instruction);
+ }
+ return unused;
+ }
+
+ /**
+ * @param from
+ * @return
+ */
+ private List<Jar> filterFrom(Instruction from, List<Jar> providers) {
+ if (from.isAny())
+ return providers;
+
+ List<Jar> np = new ArrayList<Jar>();
+ for (Iterator<Jar> i = providers.iterator(); i.hasNext();) {
+ Jar j = i.next();
+ if (from.matches(j.getName())) {
+ np.add(j);
+ }
+ }
+ return np;
+ }
+
+ /**
+ * Copy the package from the providers based on the split package strategy.
+ *
+ * @param dest
+ * @param providers
+ * @param directory
+ * @param splitStrategy
+ */
+ private void copyPackage(Jar dest, List<Jar> providers, String path, int splitStrategy) {
+ switch (splitStrategy) {
+ case SPLIT_MERGE_LAST :
+ for (Jar srce : providers) {
+ copy(dest, srce, path, true);
+ }
+ break;
+
+ case SPLIT_MERGE_FIRST :
+ for (Jar srce : providers) {
+ copy(dest, srce, path, false);
+ }
+ break;
+
+ case SPLIT_ERROR :
+ error(diagnostic(path, providers));
+ break;
+
+ case SPLIT_FIRST :
+ copy(dest, providers.get(0), path, false);
+ break;
+
+ default :
+ if (providers.size() > 1)
+ warning("%s", diagnostic(path, providers));
+ for (Jar srce : providers) {
+ copy(dest, srce, path, false);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Cop
+ *
+ * @param dest
+ * @param srce
+ * @param path
+ * @param overwriteResource
+ */
+ private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
+ dest.copy(srce, path, overwrite);
+
+ String key = path + "/bnd.info";
+ Resource r = dest.getResource(key);
+ if (r != null)
+ dest.putResource(key, new PreprocessResource(this, r));
+
+ if (hasSources()) {
+ String srcPath = "OSGI-OPT/src/" + path;
+ Map<String, Resource> srcContents = srce.getDirectories().get(srcPath);
+ if (srcContents != null) {
+ dest.addDirectory(srcContents, overwrite);
+ }
+ }
+ }
+
+ /**
+ * Analyze the classpath for a split package
+ *
+ * @param pack
+ * @param classpath
+ * @param source
+ * @return
+ */
+ private String diagnostic(String pack, List<Jar> culprits) {
+ // Default is like merge-first, but with a warning
+ return "Split package, multiple jars provide the same package:"
+ + pack
+ + "\nUse Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning\n"
+ + "Package found in " + culprits + "\n" //
+ + "Class path " + getClasspath();
+ }
+
+ private int getSplitStrategy(String type) {
+ if (type == null)
+ return SPLIT_DEFAULT;
+
+ if (type.equals("merge-last"))
+ return SPLIT_MERGE_LAST;
+
+ if (type.equals("merge-first"))
+ return SPLIT_MERGE_FIRST;
+
+ if (type.equals("error"))
+ return SPLIT_ERROR;
+
+ if (type.equals("first"))
+ return SPLIT_FIRST;
+
+ error("Invalid strategy for split-package: " + type);
+ return SPLIT_DEFAULT;
+ }
+
+ /**
+ * Matches the instructions against a package.
+ *
+ * @param instructions The list of instructions
+ * @param pack The name of the package
+ * @param unused The total list of patterns, matched patterns are removed
+ * @param source The name of the source container, can be filtered upon with
+ * the from: directive.
+ * @return
+ */
+ private Instruction matches(Instructions instructions, String pack, Set<Instruction> unused,
+ String source) {
+ for (Entry<Instruction, Attrs> entry : instructions.entrySet()) {
+ Instruction pattern = entry.getKey();
+
+ // It is possible to filter on the source of the
+ // package with the from: directive. This is an
+ // instruction that must match the name of the
+ // source class path entry.
+
+ String from = entry.getValue().get(FROM_DIRECTIVE);
+ if (from != null) {
+ Instruction f = new Instruction(from);
+ if (!f.matches(source) || f.isNegated())
+ continue;
+ }
+
+ // Now do the normal
+ // matching
+ if (pattern.matches(pack)) {
+ if (unused != null)
+ unused.remove(pattern);
+ return pattern;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Parse the Bundle-Includes header. Files in the bundles Include header are
+ * included in the jar. The source can be a directory or a file.
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+ private void doIncludeResources(Jar jar) throws Exception {
+ String includes = getProperty("Bundle-Includes");
+ if (includes == null) {
+ includes = getProperty(INCLUDERESOURCE);
+ if (includes == null || includes.length() == 0)
+ includes = getProperty("Include-Resource");
+ }
+ else
+ warning("Please use -includeresource instead of Bundle-Includes");
+
+ doIncludeResource(jar, includes);
+
+ }
+
+ private void doIncludeResource(Jar jar, String includes) throws Exception {
+ Parameters clauses = parseHeader(includes);
+ doIncludeResource(jar, clauses);
+ }
+
+ private void doIncludeResource(Jar jar, Parameters clauses) throws ZipException, IOException,
+ Exception {
+ for (Entry<String, Attrs> entry : clauses.entrySet()) {
+ doIncludeResource(jar, entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void doIncludeResource(Jar jar, String name, Map<String, String> extra)
+ throws ZipException, IOException, Exception {
+
+ boolean preprocess = false;
+ boolean absentIsOk = false;
+
+ if (name.startsWith("{") && name.endsWith("}")) {
+ preprocess = true;
+ name = name.substring(1, name.length() - 1).trim();
+ }
+
+ String parts[] = name.split("\\s*=\\s*");
+ String source = parts[0];
+ String destination = parts[0];
+ if (parts.length == 2)
+ source = parts[1];
+
+ if (source.startsWith("-")) {
+ source = source.substring(1);
+ absentIsOk = true;
+ }
+
+ if (source.startsWith("@")) {
+ extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination,
+ absentIsOk);
+ }
+ else
+ if (extra.containsKey("cmd")) {
+ doCommand(jar, source, destination, extra, preprocess, absentIsOk);
+ }
+ else
+ if (extra.containsKey("literal")) {
+ String literal = extra.get("literal");
+ Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
+ String x = extra.get("extra");
+ if (x != null)
+ r.setExtra(x);
+ jar.putResource(name, r);
+ }
+ else {
+ File sourceFile;
+ String destinationPath;
+
+ sourceFile = getFile(source);
+ if (parts.length == 1) {
+ // Directories should be copied to the root
+ // but files to their file name ...
+ if (sourceFile.isDirectory())
+ destinationPath = "";
+ else
+ destinationPath = sourceFile.getName();
+ }
+ else {
+ destinationPath = parts[0];
+ }
+ // Handle directories
+ if (sourceFile.isDirectory()) {
+ destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile,
+ destinationPath);
+ return;
+ }
+
+ // destinationPath = checkDestinationPath(destinationPath);
+
+ if (!sourceFile.exists()) {
+ if (absentIsOk)
+ return;
+
+ noSuchFile(jar, name, extra, source, destinationPath);
+ }
+ else
+ copy(jar, destinationPath, sourceFile, preprocess, extra);
+ }
+ }
+
+ /**
+ * It is possible in Include-Resource to use a system command that generates
+ * the contents, this is indicated with {@code cmd} attribute. The command
+ * can be repeated for a number of source files with the {@code for}
+ * attribute which indicates a list of repetitions, often down with the
+ * {@link Macro#_lsa(String[])} or {@link Macro#_lsb(String[])} macro. The
+ * repetition will repeat the given command for each item. The @} macro can
+ * be used to replace the current item. If no {@code for} is given, the
+ * source is used as the only item.
+ *
+ * If the destination contains a macro, each iteration will create a new
+ * file, otherwise the destination name is used.
+ *
+ * The execution of the command is delayed until the JAR is actually written
+ * to the file system for performance reasons.
+ *
+ * @param jar
+ * @param source
+ * @param destination
+ * @param extra
+ * @param preprocess
+ * @param absentIsOk
+ */
+ private void doCommand(Jar jar, String source, String destination, Map<String, String> extra,
+ boolean preprocess, boolean absentIsOk) {
+ String repeat = extra.get("for"); // TODO constant
+ if (repeat == null)
+ repeat = source;
+
+ Collection<String> requires = split(extra.get("requires"));
+ long lastModified = 0;
+ for (String required : requires) {
+ File file = getFile(required);
+ if (!file.isFile()) {
+ error("Include-Resource.cmd for %s, requires %s, but no such file %s", source,
+ required, file.getAbsoluteFile());
+ }
+ else
+ lastModified = Math.max(lastModified, file.lastModified());
+ }
+
+ String cmd = extra.get("cmd");
+
+ Collection<String> items = Processor.split(repeat);
+
+ CombinedResource cr = null;
+
+ if (!destination.contains("${@}")) {
+ cr = new CombinedResource();
+ }
+ trace("last modified requires %s", lastModified);
+
+ for (String item : items) {
+ setProperty("@", item);
+ try {
+ String path = getReplacer().process(destination);
+ String command = getReplacer().process(cmd);
+ File file = getFile(item);
+
+ Resource r = new CommandResource(command, this, Math.max(lastModified,
+ file.exists() ? file.lastModified():0L));
+
+ if (preprocess)
+ r = new PreprocessResource(this, r);
+
+ if (cr == null)
+ jar.putResource(path, r);
+ else
+ cr.addResource(r);
+ }
+ finally {
+ unsetProperty("@");
+ }
+ }
+
+ // Add last so the correct modification date is used
+ // to update the modified time.
+ if ( cr != null)
+ jar.putResource(destination, cr);
+ }
+
+ private String doResourceDirectory(Jar jar, Map<String, String> extra, boolean preprocess,
+ File sourceFile, String destinationPath) throws Exception {
+ String filter = extra.get("filter:");
+ boolean flatten = isTrue(extra.get("flatten:"));
+ boolean recursive = true;
+ String directive = extra.get("recursive:");
+ if (directive != null) {
+ recursive = isTrue(directive);
+ }
+
+ Instruction.Filter iFilter = null;
+ if (filter != null) {
+ iFilter = new Instruction.Filter(new Instruction(filter), recursive, getDoNotCopy());
+ }
+ else {
+ iFilter = new Instruction.Filter(null, recursive, getDoNotCopy());
+ }
+
+ Map<String, File> files = newMap();
+ resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);
+
+ for (Map.Entry<String, File> entry : files.entrySet()) {
+ copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
+ }
+ return destinationPath;
+ }
+
+ private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path,
+ Map<String, File> files, boolean flatten) {
+
+ if (doNotCopy(dir.getName())) {
+ return;
+ }
+
+ File[] fs = dir.listFiles(filter);
+ for (File file : fs) {
+ if (file.isDirectory()) {
+ if (recursive) {
+ String nextPath;
+ if (flatten)
+ nextPath = path;
+ else
+ nextPath = appendPath(path, file.getName());
+
+ resolveFiles(file, filter, recursive, nextPath, files, flatten);
+ }
+ // Directories are ignored otherwise
+ }
+ else {
+ String p = appendPath(path, file.getName());
+ if (files.containsKey(p))
+ warning("Include-Resource overwrites entry %s from file %s", p, file);
+ files.put(p, file);
+ }
+ }
+ }
+
+ private void noSuchFile(Jar jar, String clause, Map<String, String> extra, String source,
+ String destinationPath) throws Exception {
+ Jar src = getJarFromName(source, "Include-Resource " + source);
+ if (src != null) {
+ // Do not touch the manifest so this also
+ // works for signed files.
+ src.setDoNotTouchManifest();
+ JarResource jarResource = new JarResource(src);
+ jar.putResource(destinationPath, jarResource);
+ }
+ else {
+ Resource lastChance = make.process(source);
+ if (lastChance != null) {
+ String x = extra.get("extra");
+ if (x != null)
+ lastChance.setExtra(x);
+ jar.putResource(destinationPath, lastChance);
+ }
+ else
+ error("Input file does not exist: " + source);
+ }
+ }
+
+ /**
+ * Extra resources from a Jar and add them to the given jar. The clause is
+ * the
+ *
+ * @param jar
+ * @param clauses
+ * @param i
+ * @throws ZipException
+ * @throws IOException
+ */
+ private void extractFromJar(Jar jar, String source, String destination, boolean absentIsOk)
+ throws ZipException, IOException {
+ // Inline all resources and classes from another jar
+ // optionally appended with a modified regular expression
+ // like @zip.jar!/META-INF/MANIFEST.MF
+ int n = source.lastIndexOf("!/");
+ Instruction instr = null;
+ if (n > 0) {
+ instr = new Instruction(source.substring(n + 2));
+ source = source.substring(0, n);
+ }
+
+ // Pattern filter = null;
+ // if (n > 0) {
+ // String fstring = source.substring(n + 2);
+ // source = source.substring(0, n);
+ // filter = wildcard(fstring);
+ // }
+ Jar sub = getJarFromName(source, "extract from jar");
+ if (sub == null) {
+ if (absentIsOk)
+ return;
+
+ error("Can not find JAR file " + source);
+ }
+ else {
+ addAll(jar, sub, instr, destination);
+ }
+ }
+
+ /**
+ * Add all the resources in the given jar that match the given filter.
+ *
+ * @param sub the jar
+ * @param filter a pattern that should match the resoures in sub to be added
+ */
+ public boolean addAll(Jar to, Jar sub, Instruction filter) {
+ return addAll(to, sub, filter, "");
+ }
+
+ /**
+ * Add all the resources in the given jar that match the given filter.
+ *
+ * @param sub the jar
+ * @param filter a pattern that should match the resoures in sub to be added
+ */
+ public boolean addAll(Jar to, Jar sub, Instruction filter, String destination) {
+ boolean dupl = false;
+ for (String name : sub.getResources().keySet()) {
+ if ("META-INF/MANIFEST.MF".equals(name))
+ continue;
+
+ if (filter == null || filter.matches(name) != filter.isNegated())
+ dupl |= to.putResource(Processor.appendPath(destination, name),
+ sub.getResource(name), true);
+ }
+ return dupl;
+ }
+
+ private void copy(Jar jar, String path, File from, boolean preprocess, Map<String, String> extra)
+ throws Exception {
+ if (doNotCopy(from.getName()))
+ return;
+
+ if (from.isDirectory()) {
+
+ File files[] = from.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
+ }
+ }
+ else {
+ if (from.exists()) {
+ Resource resource = new FileResource(from);
+ if (preprocess) {
+ resource = new PreprocessResource(this, resource);
+ }
+ String x = extra.get("extra");
+ if (x != null)
+ resource.setExtra(x);
+ if (path.endsWith("/"))
+ path = path + from.getName();
+ jar.putResource(path, resource);
+
+ if (isTrue(extra.get(LIB_DIRECTIVE))) {
+ setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
+ }
+ }
+ else {
+ error("Input file does not exist: " + from);
+ }
+ }
+ }
+
+ public void setSourcepath(File[] files) {
+ for (int i = 0; i < files.length; i++)
+ addSourcepath(files[i]);
+ }
+
+ public void addSourcepath(File cp) {
+ if (!cp.exists())
+ warning("File on sourcepath that does not exist: " + cp);
+
+ sourcePath.add(cp);
+ }
+
+ public void close() {
+ super.close();
+ }
+
+ /**
+ * Build Multiple jars. If the -sub command is set, we filter the file with
+ * the given patterns.
+ *
+ * @return
+ * @throws Exception
+ */
+ public Jar[] builds() throws Exception {
+ begin();
+
+ // Are we acting as a conduit for another JAR?
+ String conduit = getProperty(CONDUIT);
+ if (conduit != null) {
+ Parameters map = parseHeader(conduit);
+ Jar[] result = new Jar[map.size()];
+ int n = 0;
+ for (String file : map.keySet()) {
+ Jar c = new Jar(getFile(file));
+ addClose(c);
+ String name = map.get(file).get("name");
+ if (name != null)
+ c.setName(name);
+
+ result[n++] = c;
+ }
+ return result;
+ }
+
+ List<Jar> result = new ArrayList<Jar>();
+ List<Builder> builders;
+
+ builders = getSubBuilders();
+
+ for (Builder builder : builders) {
+ try {
+ Jar jar = builder.build();
+ jar.setName(builder.getBsn());
+ result.add(jar);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ error("Sub Building " + builder.getBsn(), e);
+ }
+ if (builder != this)
+ getInfo(builder, builder.getBsn() + ": ");
+ }
+ return result.toArray(new Jar[result.size()]);
+ }
+
+ /**
+ * Answer a list of builders that represent this file or a list of files
+ * specified in -sub. This list can be empty. These builders represents to
+ * be created artifacts and are each scoped to such an artifacts. The
+ * builders can be used to build the bundles or they can be used to find out
+ * information about the to be generated bundles.
+ *
+ * @return List of 0..n builders representing artifacts.
+ * @throws Exception
+ */
+ public List<Builder> getSubBuilders() throws Exception {
+ String sub = getProperty(SUB);
+ if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
+ return Arrays.asList(this);
+
+ List<Builder> builders = new ArrayList<Builder>();
+ if (isTrue(getProperty(NOBUNDLES)))
+ return builders;
+
+ Parameters subsMap = parseHeader(sub);
+ for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
+ File file = getFile(i.next());
+ if (file.isFile()) {
+ builders.add(getSubBuilder(file));
+ i.remove();
+ }
+ }
+
+ Instructions instructions = new Instructions(subsMap);
+
+ List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
+
+ nextFile: while (members.size() > 0) {
+
+ File file = members.remove(0);
+
+ // Check if the file is one of our parents
+ Processor p = this;
+ while (p != null) {
+ if (file.equals(p.getPropertiesFile()))
+ continue nextFile;
+ p = p.getParent();
+ }
+
+ for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {
+
+ Instruction instruction = i.next();
+ if (instruction.matches(file.getName())) {
+
+ if (!instruction.isNegated()) {
+ builders.add(getSubBuilder(file));
+ }
+
+ // Because we matched (even though we could be negated)
+ // we skip any remaining searches
+ continue nextFile;
+ }
+ }
+ }
+ return builders;
+ }
+
+ public Builder getSubBuilder(File file) throws Exception {
+ Builder builder = getSubBuilder();
+ if (builder != null) {
+ builder.setProperties(file);
+ addClose(builder);
+ }
+ return builder;
+ }
+
+ public Builder getSubBuilder() throws Exception {
+ Builder builder = new Builder(this);
+ builder.setBase(getBase());
+
+ for (Jar file : getClasspath()) {
+ builder.addClasspath(file);
+ }
+
+ return builder;
+ }
+
+ /**
+ * A macro to convert a maven version to an OSGi version
+ */
+
+ public String _maven_version(String args[]) {
+ if (args.length > 2)
+ error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
+ else
+ if (args.length < 2)
+ error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
+ else {
+ return cleanupVersion(args[1]);
+ }
+ return null;
+ }
+
+ public String _permissions(String args[]) throws IOException {
+ StringBuilder sb = new StringBuilder();
+
+ for (String arg : args) {
+ if ("packages".equals(arg) || "all".equals(arg)) {
+ for (PackageRef imp : getImports().keySet()) {
+ if (!imp.isJava()) {
+ sb.append("(org.osgi.framework.PackagePermission \"");
+ sb.append(imp);
+ sb.append("\" \"import\")\r\n");
+ }
+ }
+ for (PackageRef exp : getExports().keySet()) {
+ sb.append("(org.osgi.framework.PackagePermission \"");
+ sb.append(exp);
+ sb.append("\" \"export\")\r\n");
+ }
+ }
+ else
+ if ("admin".equals(arg) || "all".equals(arg)) {
+ sb.append("(org.osgi.framework.AdminPermission)");
+ }
+ else
+ if ("permissions".equals(arg))
+ ;
+ else
+ error("Invalid option in ${permissions}: %s", arg);
+ }
+ return sb.toString();
+ }
+
+ /**
+ *
+ */
+ public void removeBundleSpecificHeaders() {
+ Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
+ setForceLocal(set);
+ }
+
+ /**
+ * Check if the given resource is in scope of this bundle. That is, it
+ * checks if the Include-Resource includes this resource or if it is a class
+ * file it is on the class path and the Export-Pacakge or Private-Package
+ * include this resource.
+ *
+ * @param f
+ * @return
+ */
+ public boolean isInScope(Collection<File> resources) throws Exception {
+ Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES,
+ "test;presence:=optional")));
+ }
+
+ Collection<String> ir = getIncludedResourcePrefixes();
+
+ Instructions instructions = new Instructions(clauses);
+
+ for (File r : resources) {
+ String cpEntry = getClasspathEntrySuffix(r);
+ if (cpEntry != null) {
+ String pack = Descriptors.getPackage(cpEntry);
+ Instruction i = matches(instructions, pack, null, r.getName());
+ if (i != null)
+ return !i.isNegated();
+ }
+
+ // Check if this resource starts with one of the I-C header
+ // paths.
+ String path = r.getAbsolutePath();
+ for (String p : ir) {
+ if (path.startsWith(p))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Extra the paths for the directories and files that are used in the
+ * Include-Resource header.
+ *
+ * @return
+ */
+ private Collection<String> getIncludedResourcePrefixes() {
+ List<String> prefixes = new ArrayList<String>();
+ Parameters includeResource = getIncludeResource();
+ for (Entry<String, Attrs> p : includeResource.entrySet()) {
+ if (p.getValue().containsKey("literal"))
+ continue;
+
+ Matcher m = IR_PATTERN.matcher(p.getKey());
+ if (m.matches()) {
+ File f = getFile(m.group(1));
+ prefixes.add(f.getAbsolutePath());
+ }
+ }
+ return prefixes;
+ }
+
+ /**
+ * Answer the string of the resource that it has in the container.
+ *
+ * @param resource The resource to look for
+ * @return
+ * @throws Exception
+ */
+ public String getClasspathEntrySuffix(File resource) throws Exception {
+ for (Jar jar : getClasspath()) {
+ File source = jar.getSource();
+ if (source != null) {
+ source = source.getCanonicalFile();
+ String sourcePath = source.getAbsolutePath();
+ String resourcePath = resource.getAbsolutePath();
+
+ if (resourcePath.startsWith(sourcePath)) {
+ // Make sure that the path name is translated correctly
+ // i.e. on Windows the \ must be translated to /
+ String filePath = resourcePath.substring(sourcePath.length() + 1);
+
+ return filePath.replace(File.separatorChar, '/');
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * doNotCopy
+ *
+ * The doNotCopy variable maintains a patter for files that should not be
+ * copied. There is a default {@link #DEFAULT_DO_NOT_COPY} but this ca be
+ * overridden with the {@link Constants#DONOTCOPY} property.
+ */
+
+ public boolean doNotCopy(String v) {
+ return getDoNotCopy().matcher(v).matches();
+ }
+
+ public Pattern getDoNotCopy() {
+ if (xdoNotCopy == null) {
+ String string = null;
+ try {
+ string = getProperty(DONOTCOPY, DEFAULT_DO_NOT_COPY);
+ xdoNotCopy = Pattern.compile(string);
+ }
+ catch (Exception e) {
+ error("Invalid value for %s, value is %s", DONOTCOPY, string);
+ xdoNotCopy = Pattern.compile(DEFAULT_DO_NOT_COPY);
+ }
+ }
+ return xdoNotCopy;
+ }
+
+ /**
+ */
+
+ static MakeBnd makeBnd = new MakeBnd();
+ static MakeCopy makeCopy = new MakeCopy();
+ static ServiceComponent serviceComponent = new ServiceComponent();
+ static DSAnnotations dsAnnotations = new DSAnnotations();
+ static MetatypePlugin metatypePlugin = new MetatypePlugin();
+
+ @Override
+ protected void setTypeSpecificPlugins(Set<Object> list) {
+ list.add(makeBnd);
+ list.add(makeCopy);
+ list.add(serviceComponent);
+ list.add(dsAnnotations);
+ list.add(metatypePlugin);
+ super.setTypeSpecificPlugins(list);
+ }
+
+ /**
+ * Diff this bundle to another bundle for the given packages.
+ *
+ * @throws Exception
+ */
+
+ public void doDiff(Jar dot) throws Exception {
+ Parameters diffs = parseHeader(getProperty("-diff"));
+ if (diffs.isEmpty())
+ return;
+
+ trace("diff %s", diffs);
+
+ if (tree == null)
+ tree = differ.tree(this);
+
+ for (Entry<String, Attrs> entry : diffs.entrySet()) {
+ String path = entry.getKey();
+ File file = getFile(path);
+ if (!file.isFile()) {
+ error("Diffing against %s that is not a file", file);
+ continue;
+ }
+
+ boolean full = entry.getValue().get("--full") != null;
+ boolean warning = entry.getValue().get("--warning") != null;
+
+ Tree other = differ.tree(file);
+ Diff api = tree.diff(other).get("<api>");
+ Instructions instructions = new Instructions(entry.getValue().get("--pack"));
+
+ trace("diff against %s --full=%s --pack=%s --warning=%s", file, full, instructions);
+ for (Diff p : api.getChildren()) {
+ String pname = p.getName();
+ if (p.getType() == Type.PACKAGE && instructions.matches(pname)) {
+ if (p.getDelta() != Delta.UNCHANGED) {
+
+ if (!full)
+ if (warning)
+ warning("Differ %s", p);
+ else
+ error("Differ %s", p);
+ else {
+ if (warning)
+ warning("Diff found a difference in %s for packages %s", file,
+ instructions);
+ else
+ error("Diff found a difference in %s for packages %s", file,
+ instructions);
+ show(p, "", warning);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Show the diff recursively
+ *
+ * @param p
+ * @param i
+ */
+ private void show(Diff p, String indent, boolean warning) {
+ Delta d = p.getDelta();
+ if (d == Delta.UNCHANGED)
+ return;
+
+ if (warning)
+ warning("%s%s", indent, p);
+ else
+ error("%s%s", indent, p);
+
+ indent = indent + " ";
+ switch (d) {
+ case CHANGED :
+ case MAJOR :
+ case MINOR :
+ case MICRO :
+ break;
+
+ default :
+ return;
+ }
+ for (Diff c : p.getChildren())
+ show(c, indent, warning);
+ }
+
+ /**
+ * Base line against a previous version
+ *
+ * @throws Exception
+ */
+
+ private void doBaseline(Jar dot) throws Exception {
+ Parameters diffs = parseHeader(getProperty("-baseline"));
+ if (diffs.isEmpty())
+ return;
+
+ System.err.printf("baseline %s%n", diffs);
+
+ Baseline baseline = new Baseline(this, differ);
+
+ for (Entry<String, Attrs> entry : diffs.entrySet()) {
+ String path = entry.getKey();
+ File file = getFile(path);
+ if (!file.isFile()) {
+ error("Diffing against %s that is not a file", file);
+ continue;
+ }
+ Jar other = new Jar(file);
+ Set<Info> infos = baseline.baseline(dot, other, null);
+ for (Info info : infos) {
+ if (info.mismatch) {
+ error("%s %-50s %-10s %-10s %-10s %-10s %-10s\n", info.mismatch ? '*' : ' ',
+ info.packageName, info.packageDiff.getDelta(), info.newerVersion,
+ info.olderVersion, info.suggestedVersion,
+ info.suggestedIfProviders == null ? "-" : info.suggestedIfProviders);
+ }
+ }
+ }
+ }
+
+ public void addSourcepath(Collection<File> sourcepath) {
+ for (File f : sourcepath) {
+ addSourcepath(f);
+ }
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/BundleId.java b/bundleplugin/src/main/java/aQute/lib/osgi/BundleId.java
new file mode 100644
index 0000000..e500e53
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/BundleId.java
@@ -0,0 +1,44 @@
+package aQute.lib.osgi;
+
+
+/**
+ * Holds the bundle bsn + version pair
+ *
+ */
+public class BundleId implements Comparable<BundleId> {
+ final String bsn;
+ final String version;
+
+ public BundleId(String bsn, String version) {
+ this.bsn = bsn.trim();
+ this.version = version.trim();
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getBsn() {
+ return bsn;
+ }
+
+ public boolean isValid() {
+ return Verifier.isVersion(version) && Verifier.isBsn(bsn);
+ }
+
+ public boolean equals(Object o) {
+ return this == o || ((o instanceof BundleId) && compareTo((BundleId) o) == 0);
+ }
+
+ public int hashCode() {
+ return bsn.hashCode() ^ version.hashCode();
+ }
+
+ public int compareTo(BundleId other) {
+ int result = bsn.compareTo(other.bsn);
+ if ( result != 0)
+ return result;
+
+ return version.compareTo(other.version);
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java b/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java
new file mode 100644
index 0000000..c878665
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java
@@ -0,0 +1,91 @@
+package aQute.lib.osgi;
+
+import aQute.lib.osgi.Descriptors.TypeRef;
+
+public class ClassDataCollector {
+ public void classBegin(int access, TypeRef name) {
+ }
+
+ public boolean classStart(int access, TypeRef className) {
+ classBegin(access,className);
+ return true;
+ }
+
+ public void extendsClass(TypeRef zuper) throws Exception {
+ }
+
+ public void implementsInterfaces(TypeRef[] interfaces) throws Exception {
+ }
+
+ public void addReference(TypeRef ref) {
+ }
+
+ public void annotation(Annotation annotation) {
+ }
+
+ public void parameter(int p) {
+ }
+
+ public void method(Clazz.MethodDef defined) {
+ }
+
+ public void field(Clazz.FieldDef defined) {
+ }
+
+ public void reference(Clazz.MethodDef referenced) {
+ }
+
+ public void reference(Clazz.FieldDef referenced) {
+ }
+
+ public void classEnd() throws Exception {
+ }
+
+ public void deprecated() throws Exception {
+ }
+
+
+ /**
+ * The EnclosingMethod attribute
+ *
+ * @param cName The name of the enclosing class, never null. Name is with slashes.
+ * @param mName The name of the enclosing method in the class with cName or null
+ * @param mDescriptor The descriptor of this type
+ */
+ public void enclosingMethod(TypeRef cName, String mName, String mDescriptor) {
+
+ }
+
+ /**
+ * The InnerClass attribute
+ *
+ * @param innerClass The name of the inner class (with slashes). Can be null.
+ * @param outerClass The name of the outer class (with slashes) Can be null.
+ * @param innerName The name inside the outer class, can be null.
+ * @param modifiers The access flags
+ * @throws Exception
+ */
+ public void innerClass(TypeRef innerClass, TypeRef outerClass, String innerName,
+ int innerClassAccessFlags) throws Exception {
+ }
+
+ public void signature(String signature) {
+ }
+
+ public void constant(Object object) {
+ }
+
+ public void memberEnd() {
+ }
+
+ public void version(int minor, int major) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void referenceMethod(int access, TypeRef className, String method, String descriptor) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java b/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java
new file mode 100755
index 0000000..e182e1f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java
@@ -0,0 +1,1645 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.nio.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.lib.osgi.Descriptors.Descriptor;
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.generics.*;
+
+public class Clazz {
+
+ static Pattern METHOD_DESCRIPTOR = Pattern.compile("\\((.*)\\)(.+)");
+
+ public class ClassConstant {
+ int cname;
+
+ public ClassConstant(int class_index) {
+ this.cname = class_index;
+ }
+
+ public String getName() {
+ return (String) pool[cname];
+ }
+ }
+
+ public static enum JAVA {
+ JDK1_1(45, "JRE-1.1"), JDK1_2(46, "J2SE-1.2"), //
+ JDK1_3(47, "J2SE-1.3"), //
+ JDK1_4(48, "J2SE-1.4"), //
+ J2SE5(49, "J2SE-1.5"), //
+ J2SE6(50, "JavaSE-1.6"), //
+ OpenJDK7(51, "JavaSE-1.7"), //
+ UNKNOWN(Integer.MAX_VALUE, "<>")//
+ ;
+
+ final int major;
+ final String ee;
+
+ JAVA(int major, String ee) {
+ this.major = major;
+ this.ee = ee;
+ }
+
+ static JAVA format(int n) {
+ for (JAVA e : JAVA.values())
+ if (e.major == n)
+ return e;
+ return UNKNOWN;
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public boolean hasAnnotations() {
+ return major >= J2SE5.major;
+ }
+
+ public boolean hasGenerics() {
+ return major >= J2SE5.major;
+ }
+
+ public boolean hasEnums() {
+ return major >= J2SE5.major;
+ }
+
+ public static JAVA getJava(int major, int minor) {
+ for (JAVA j : JAVA.values()) {
+ if (j.major == major)
+ return j;
+ }
+ return UNKNOWN;
+ }
+
+ public String getEE() {
+ return ee;
+ }
+ }
+
+ public static enum QUERY {
+ IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION, CONCRETE, ABSTRACT, PUBLIC, ANNOTATED, RUNTIMEANNOTATIONS, CLASSANNOTATIONS;
+
+ }
+
+ public final static EnumSet<QUERY> HAS_ARGUMENT = EnumSet.of(QUERY.IMPLEMENTS,
+ QUERY.EXTENDS, QUERY.IMPORTS,
+ QUERY.NAMED, QUERY.VERSION,
+ QUERY.ANNOTATED);
+
+ /**
+ * <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
+
+ // a thing not in the source code
+ final static int ACC_SYNTHETIC = 0x1000;
+ final static int ACC_ANNOTATION = 0x2000;
+ final static int ACC_ENUM = 0x4000;
+
+ static protected class Assoc {
+ Assoc(byte tag, int a, int b) {
+ this.tag = tag;
+ this.a = a;
+ this.b = b;
+ }
+
+ byte tag;
+ int a;
+ int b;
+ }
+
+ public class Def {
+ final int access;
+ Set<TypeRef> annotations;
+
+ public Def(int access) {
+ this.access = access;
+ }
+
+ public int getAccess() {
+ return access;
+ }
+
+ public boolean isEnum() {
+ return (access & ACC_ENUM) != 0;
+ }
+
+ public boolean isPublic() {
+ return Modifier.isPublic(access);
+ }
+
+ public boolean isAbstract() {
+ return Modifier.isAbstract(access);
+ }
+
+ public boolean isProtected() {
+ return Modifier.isProtected(access);
+ }
+
+ public boolean isFinal() {
+ return Modifier.isFinal(access) || Clazz.this.isFinal();
+ }
+
+ public boolean isStatic() {
+ return Modifier.isStatic(access);
+ }
+
+ public boolean isPrivate() {
+ return Modifier.isPrivate(access);
+ }
+
+ public boolean isNative() {
+ return Modifier.isNative(access);
+ }
+
+ public boolean isTransient() {
+ return Modifier.isTransient(access);
+ }
+
+ public boolean isVolatile() {
+ return Modifier.isVolatile(access);
+ }
+
+ public boolean isInterface() {
+ return Modifier.isInterface(access);
+ }
+
+ public boolean isSynthetic() {
+ return (access & ACC_SYNTHETIC) != 0;
+ }
+
+ void addAnnotation(Annotation a) {
+ if (annotations == null)
+ annotations = Create.set();
+ annotations.add(analyzer.getTypeRef(a.name.getBinary()));
+ }
+
+ public Collection<TypeRef> getAnnotations() {
+ return annotations;
+ }
+ }
+
+ public class FieldDef extends Def {
+ final String name;
+ final Descriptor descriptor;
+ String signature;
+ Object constant;
+ boolean deprecated;
+
+ public boolean isDeprecated() {
+ return deprecated;
+ }
+
+ public void setDeprecated(boolean deprecated) {
+ this.deprecated = deprecated;
+ }
+
+ public FieldDef(int access, String name, String descriptor) {
+ super(access);
+ this.name = name;
+ this.descriptor = analyzer.getDescriptor(descriptor);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return getName();
+ }
+
+ public TypeRef getType() {
+ return descriptor.getType();
+ }
+
+ public TypeRef getContainingClass() {
+ return getClassName();
+ }
+
+ public Descriptor getDescriptor() {
+ return descriptor;
+ }
+
+ public void setConstant(Object o) {
+ this.constant = o;
+ }
+
+ public Object getConstant() {
+ return this.constant;
+ }
+
+ // TODO change to use proper generics
+ public String getGenericReturnType() {
+ String use = descriptor.toString();
+ if (signature != null)
+ use = signature;
+
+ Matcher m = METHOD_DESCRIPTOR.matcher(use);
+ if (!m.matches())
+ throw new IllegalArgumentException("Not a valid method descriptor: " + descriptor);
+
+ String returnType = m.group(2);
+ return objectDescriptorToFQN(returnType);
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
+ }
+
+ public class MethodDef extends FieldDef {
+ public MethodDef(int access, String method, String descriptor) {
+ super(access, method, descriptor);
+ }
+
+ public boolean isConstructor() {
+ return name.equals("<init>") || name.equals("<clinit>");
+ }
+
+ public TypeRef[] getPrototype() {
+ return descriptor.getPrototype();
+ }
+
+ }
+
+ final static byte SkipTable[] = { //
+ 0, // 0 non existent
+ -1, // 1 CONSTANT_utf8 UTF 8, handled in
+ // method
+ -1, // 2
+ 4, // 3 CONSTANT_Integer
+ 4, // 4 CONSTANT_Float
+ 8, // 5 CONSTANT_Long (index +=2!)
+ 8, // 6 CONSTANT_Double (index +=2!)
+ -1, // 7 CONSTANT_Class
+ 2, // 8 CONSTANT_String
+ 4, // 9 CONSTANT_FieldRef
+ 4, // 10 CONSTANT_MethodRef
+ 4, // 11 CONSTANT_InterfaceMethodRef
+ 4, // 12 CONSTANT_NameAndType
+ -1, // 13 Not defined
+ -1, // 14 Not defined
+ 3, // 15 CONSTANT_MethodHandle
+ 2, // 16 CONSTANT_MethodType
+ -1, // 17 Not defined
+ 4, // 18 CONSTANT_InvokeDynamic
+ };
+
+ boolean hasRuntimeAnnotations;
+ boolean hasClassAnnotations;
+
+ TypeRef className;
+ Object pool[];
+ int intPool[];
+ Set<PackageRef> imports = Create.set();
+ String path;
+ int minor = 0;
+ int major = 0;
+ int innerAccess = -1;
+ int accessx = 0;
+ String sourceFile;
+ Set<TypeRef> xref;
+ Set<Integer> classes;
+ Set<Integer> descriptors;
+ Set<TypeRef> annotations;
+ int forName = 0;
+ int class$ = 0;
+ TypeRef[] interfaces;
+ TypeRef zuper;
+ ClassDataCollector cd = null;
+ Resource resource;
+ FieldDef last = null;
+ boolean deprecated;
+
+ final Analyzer analyzer;
+
+ public Clazz(Analyzer analyzer, String path, Resource resource) {
+ this.path = path;
+ this.resource = resource;
+ this.analyzer = analyzer;
+ }
+
+ public Set<TypeRef> parseClassFile() throws Exception {
+ return parseClassFileWithCollector(null);
+ }
+
+ public Set<TypeRef> parseClassFile(InputStream in) throws Exception {
+ return parseClassFile(in, null);
+ }
+
+ public Set<TypeRef> parseClassFileWithCollector(ClassDataCollector cd) throws Exception {
+ InputStream in = resource.openInputStream();
+ try {
+ return parseClassFile(in, cd);
+ } finally {
+ in.close();
+ }
+ }
+
+ public Set<TypeRef> parseClassFile(InputStream in, ClassDataCollector cd) throws Exception {
+ DataInputStream din = new DataInputStream(in);
+ try {
+ this.cd = cd;
+ return parseClassFile(din);
+ } finally {
+ cd = null;
+ din.close();
+ }
+ }
+
+ Set<TypeRef> parseClassFile(DataInputStream in) throws Exception {
+ xref = new HashSet<TypeRef>();
+ classes = new HashSet<Integer>();
+ descriptors = new HashSet<Integer>();
+
+ boolean crawl = cd != null; // Crawl the byte code if we have a
+ // collector
+ int magic = in.readInt();
+ if (magic != 0xCAFEBABE)
+ throw new IOException("Not a valid class file (no CAFEBABE header)");
+
+ minor = in.readUnsignedShort(); // minor version
+ major = in.readUnsignedShort(); // major version
+ if (cd != null)
+ cd.version(minor, major);
+ int count = in.readUnsignedShort();
+ pool = new Object[count];
+ intPool = new int[count];
+
+ process: for (int poolIndex = 1; poolIndex < count; poolIndex++) {
+ byte tag = in.readByte();
+ switch (tag) {
+ case 0:
+ break process;
+ case 1:
+ constantUtf8(in, poolIndex);
+ break;
+
+ case 3:
+ constantInteger(in, poolIndex);
+ break;
+
+ case 4:
+ constantFloat(in, poolIndex);
+ break;
+
+ // For some insane optimization reason are
+ // the long and the double two entries in the
+ // constant pool. See 4.4.5
+ case 5:
+ constantLong(in, poolIndex);
+ poolIndex++;
+ break;
+
+ case 6:
+ constantDouble(in, poolIndex);
+ poolIndex++;
+ break;
+
+ case 7:
+ constantClass(in, poolIndex);
+ break;
+
+ case 8:
+ constantString(in, poolIndex);
+ break;
+
+ case 10: // Method ref
+ case 11: // Interface Method ref
+ methodRef(in, poolIndex);
+ break;
+
+ // Name and Type
+ case 12:
+ nameAndType(in, poolIndex, tag);
+ break;
+
+ // We get the skip count for each record type
+ // from the SkipTable. This will also automatically
+ // abort when
+ default:
+ if (tag == 2)
+ throw new IOException("Invalid tag " + tag);
+ in.skipBytes(SkipTable[tag]);
+ break;
+ }
+ }
+
+ pool(pool, intPool);
+ /*
+ * Parse after the constant pool, code thanks to Hans Christian
+ * Falkenberg
+ */
+
+ accessx = in.readUnsignedShort(); // access
+
+ int this_class = in.readUnsignedShort();
+ className = analyzer.getTypeRef((String) pool[intPool[this_class]]);
+
+ try {
+
+ if (cd != null) {
+ if (!cd.classStart(accessx, className))
+ return null;
+ }
+
+ int super_class = in.readUnsignedShort();
+ String superName = (String) pool[intPool[super_class]];
+ if (superName != null) {
+ zuper = analyzer.getTypeRef(superName);
+ }
+
+ if (zuper != null) {
+ referTo(zuper);
+ if (cd != null)
+ cd.extendsClass(zuper);
+ }
+
+ int interfacesCount = in.readUnsignedShort();
+ if (interfacesCount > 0) {
+ interfaces = new TypeRef[interfacesCount];
+ for (int i = 0; i < interfacesCount; i++)
+ interfaces[i] = analyzer.getTypeRef((String) pool[intPool[in
+ .readUnsignedShort()]]);
+ if (cd != null)
+ cd.implementsInterfaces(interfaces);
+ }
+
+ int fieldsCount = in.readUnsignedShort();
+ for (int i = 0; i < fieldsCount; i++) {
+ int access_flags = in.readUnsignedShort(); // skip access flags
+ int name_index = in.readUnsignedShort();
+ int descriptor_index = in.readUnsignedShort();
+
+ // Java prior to 1.5 used a weird
+ // static variable to hold the com.X.class
+ // result construct. If it did not find it
+ // it would create a variable class$com$X
+ // that would be used to hold the class
+ // object gotten with Class.forName ...
+ // Stupidly, they did not actively use the
+ // class name for the field type, so bnd
+ // would not see a reference. We detect
+ // this case and add an artificial descriptor
+ String name = pool[name_index].toString(); // name_index
+ if (name.startsWith("class$")) {
+ crawl = true;
+ }
+ if (cd != null)
+ cd.field(last = new FieldDef(access_flags, name, pool[descriptor_index]
+ .toString()));
+ descriptors.add(Integer.valueOf(descriptor_index));
+ doAttributes(in, ElementType.FIELD, false);
+ }
+
+ //
+ // Check if we have to crawl the code to find
+ // the ldc(_w) <string constant> invokestatic Class.forName
+ // if so, calculate the method ref index so we
+ // can do this efficiently
+ //
+ if (crawl) {
+ forName = findMethodReference("java/lang/Class", "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ class$ = findMethodReference(className.getBinary(), "class$",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ } else if (major == 48 ) {
+ forName = findMethodReference("java/lang/Class", "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ if (forName > 0) {
+ crawl = true;
+ class$ = findMethodReference(className.getBinary(), "class$",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ }
+ }
+
+ // There are some serious changes in the
+ // class file format. So we do not do any crawling
+ // it has also become less important
+ if ( major >= JAVA.OpenJDK7.major )
+ crawl = false;
+
+ //
+ // Handle the methods
+ //
+ int methodCount = in.readUnsignedShort();
+ for (int i = 0; i < methodCount; i++) {
+ int access_flags = in.readUnsignedShort();
+ int name_index = in.readUnsignedShort();
+ int descriptor_index = in.readUnsignedShort();
+ descriptors.add(Integer.valueOf(descriptor_index));
+ String name = pool[name_index].toString();
+ String descriptor = pool[descriptor_index].toString();
+ if (cd != null) {
+ MethodDef mdef = new MethodDef(access_flags, name, descriptor);
+ last = mdef;
+ cd.method(mdef);
+ }
+
+ if ("<init>".equals(name)) {
+ doAttributes(in, ElementType.CONSTRUCTOR, crawl);
+ } else {
+ doAttributes(in, ElementType.METHOD, crawl);
+ }
+ }
+ if (cd != null)
+ cd.memberEnd();
+
+ doAttributes(in, ElementType.TYPE, false);
+
+ //
+ // Now iterate over all classes we found and
+ // parse those as well. We skip duplicates
+ //
+
+ for (int n : classes) {
+ String descr = (String) pool[n];
+
+ TypeRef clazz = analyzer.getTypeRef(descr);
+ referTo(clazz);
+ }
+
+ //
+ // Parse all the descriptors we found
+ //
+
+ for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
+ Integer index = e.next();
+ String prototype = (String) pool[index.intValue()];
+ if (prototype != null)
+ parseDescriptor(prototype);
+ else
+ System.err.println("Unrecognized descriptor: " + index);
+ }
+ Set<TypeRef> xref = this.xref;
+ reset();
+ return xref;
+ } finally {
+ if (cd != null)
+ cd.classEnd();
+ }
+ }
+
+ private void constantFloat(DataInputStream in, int poolIndex) throws IOException {
+ if (cd != null)
+ pool[poolIndex] = in.readFloat(); // ALU
+ else
+ in.skipBytes(4);
+ }
+
+ private void constantInteger(DataInputStream in, int poolIndex) throws IOException {
+ intPool[poolIndex] = in.readInt();
+ if (cd != null)
+ pool[poolIndex] = intPool[poolIndex];
+ }
+
+ protected void pool(Object[] pool, int[] intPool) {
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @param tag
+ * @throws IOException
+ */
+ protected void nameAndType(DataInputStream in, int poolIndex, byte tag) throws IOException {
+ int name_index = in.readUnsignedShort();
+ int descriptor_index = in.readUnsignedShort();
+ descriptors.add(Integer.valueOf(descriptor_index));
+ pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @param tag
+ * @throws IOException
+ */
+ private void methodRef(DataInputStream in, int poolIndex) throws IOException {
+ int class_index = in.readUnsignedShort();
+ int name_and_type_index = in.readUnsignedShort();
+ pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @throws IOException
+ */
+ private void constantString(DataInputStream in, int poolIndex) throws IOException {
+ int string_index = in.readUnsignedShort();
+ intPool[poolIndex] = string_index;
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @throws IOException
+ */
+ protected void constantClass(DataInputStream in, int poolIndex) throws IOException {
+ int class_index = in.readUnsignedShort();
+ classes.add(Integer.valueOf(class_index));
+ intPool[poolIndex] = class_index;
+ ClassConstant c = new ClassConstant(class_index);
+ pool[poolIndex] = c;
+ }
+
+ /**
+ * @param in
+ * @throws IOException
+ */
+ protected void constantDouble(DataInputStream in, int poolIndex) throws IOException {
+ if (cd != null)
+ pool[poolIndex] = in.readDouble();
+ else
+ in.skipBytes(8);
+ }
+
+ /**
+ * @param in
+ * @throws IOException
+ */
+ protected void constantLong(DataInputStream in, int poolIndex) throws IOException {
+ if (cd != null) {
+ pool[poolIndex] = in.readLong();
+ } else
+ in.skipBytes(8);
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @throws IOException
+ */
+ protected void constantUtf8(DataInputStream in, int poolIndex) throws IOException {
+ // CONSTANT_Utf8
+
+ String name = in.readUTF();
+ pool[poolIndex] = name;
+ }
+
+ /**
+ * Find a method reference in the pool that points to the given class,
+ * methodname and descriptor.
+ *
+ * @param clazz
+ * @param methodname
+ * @param descriptor
+ * @return index in constant pool
+ */
+ private int findMethodReference(String clazz, String methodname, String descriptor) {
+ for (int i = 1; i < pool.length; i++) {
+ if (pool[i] instanceof Assoc) {
+ Assoc methodref = (Assoc) pool[i];
+ if (methodref.tag == 10) {
+ // Method ref
+ int class_index = methodref.a;
+ int class_name_index = intPool[class_index];
+ if (clazz.equals(pool[class_name_index])) {
+ int name_and_type_index = methodref.b;
+ Assoc name_and_type = (Assoc) pool[name_and_type_index];
+ if (name_and_type.tag == 12) {
+ // Name and Type
+ int name_index = name_and_type.a;
+ int type_index = name_and_type.b;
+ if (methodname.equals(pool[name_index])) {
+ if (descriptor.equals(pool[type_index])) {
+ return i;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Called for each attribute in the class, field, or method.
+ *
+ * @param in
+ * The stream
+ * @throws Exception
+ */
+ private void doAttributes(DataInputStream in, ElementType member, boolean crawl)
+ throws Exception {
+ int attributesCount = in.readUnsignedShort();
+ for (int j = 0; j < attributesCount; j++) {
+ // skip name CONSTANT_Utf8 pointer
+ doAttribute(in, member, crawl);
+ }
+ }
+
+ /**
+ * Process a single attribute, if not recognized, skip it.
+ *
+ * @param in
+ * the data stream
+ * @throws Exception
+ */
+ private void doAttribute(DataInputStream in, ElementType member, boolean crawl)
+ throws Exception {
+ int attribute_name_index = in.readUnsignedShort();
+ String attributeName = (String) pool[attribute_name_index];
+ long attribute_length = in.readInt();
+ attribute_length &= 0xFFFFFFFF;
+ if ("Deprecated".equals(attributeName)) {
+ if (cd != null)
+ cd.deprecated();
+ } else if ("RuntimeVisibleAnnotations".equals(attributeName))
+ doAnnotations(in, member, RetentionPolicy.RUNTIME);
+ else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
+ doParameterAnnotations(in, member, RetentionPolicy.RUNTIME);
+ else if ("RuntimeInvisibleAnnotations".equals(attributeName))
+ doAnnotations(in, member, RetentionPolicy.CLASS);
+ else if ("RuntimeInvisibleParameterAnnotations".equals(attributeName))
+ doParameterAnnotations(in, member, RetentionPolicy.CLASS);
+ else if ("InnerClasses".equals(attributeName))
+ doInnerClasses(in);
+ else if ("EnclosingMethod".equals(attributeName))
+ doEnclosingMethod(in);
+ else if ("SourceFile".equals(attributeName))
+ doSourceFile(in);
+ else if ("Code".equals(attributeName) && crawl)
+ doCode(in);
+ else if ("Signature".equals(attributeName))
+ doSignature(in, member);
+ else if ("ConstantValue".equals(attributeName))
+ doConstantValue(in);
+ else {
+ if (attribute_length > 0x7FFFFFFF) {
+ throw new IllegalArgumentException("Attribute > 2Gb");
+ }
+ in.skipBytes((int) attribute_length);
+ }
+ }
+
+ /**
+ * <pre>
+ * EnclosingMethod_attribute {
+ * u2 attribute_name_index;
+ * u4 attribute_length;
+ * u2 class_index
+ * u2 method_index;
+ * }
+ * </pre>
+ *
+ *
+ * @param in
+ * @throws IOException
+ */
+ private void doEnclosingMethod(DataInputStream in) throws IOException {
+ int cIndex = in.readShort();
+ int mIndex = in.readShort();
+
+ if (cd != null) {
+ int nameIndex = intPool[cIndex];
+ TypeRef cName = analyzer.getTypeRef((String) pool[nameIndex]);
+
+ String mName = null;
+ String mDescriptor = null;
+
+ if (mIndex != 0) {
+ Assoc nameAndType = (Assoc) pool[mIndex];
+ mName = (String) pool[nameAndType.a];
+ mDescriptor = (String) pool[nameAndType.b];
+ }
+ cd.enclosingMethod(cName, mName, mDescriptor);
+ }
+ }
+
+ /**
+ * <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>
+ *
+ * @param in
+ * @throws Exception
+ */
+ private void doInnerClasses(DataInputStream in) throws Exception {
+ int number_of_classes = in.readShort();
+ for (int i = 0; i < number_of_classes; i++) {
+ int inner_class_info_index = in.readShort();
+ int outer_class_info_index = in.readShort();
+ int inner_name_index = in.readShort();
+ int inner_class_access_flags = in.readShort() & 0xFFFF;
+
+ if (cd != null) {
+ TypeRef innerClass = null;
+ TypeRef outerClass = null;
+ String innerName = null;
+
+ if (inner_class_info_index != 0) {
+ int nameIndex = intPool[inner_class_info_index];
+ innerClass = analyzer.getTypeRef((String) pool[nameIndex]);
+ }
+
+ if (outer_class_info_index != 0) {
+ int nameIndex = intPool[outer_class_info_index];
+ outerClass = analyzer.getTypeRef((String) pool[nameIndex]);
+ }
+
+ if (inner_name_index != 0)
+ innerName = (String) pool[inner_name_index];
+
+ cd.innerClass(innerClass, outerClass, innerName, inner_class_access_flags);
+ }
+ }
+ }
+
+ /**
+ * Handle a signature
+ *
+ * <pre>
+ * Signature_attribute {
+ * u2 attribute_name_index;
+ * u4 attribute_length;
+ * u2 signature_index;
+ * }
+ * </pre>
+ *
+ * @param member
+ */
+
+ void doSignature(DataInputStream in, ElementType member) throws IOException {
+ int signature_index = in.readUnsignedShort();
+ String signature = (String) pool[signature_index];
+
+ // s.println("Signature " + signature );
+
+ // // The type signature is kind of weird,
+ // // lets skip it for now. Seems to be some kind of
+ // // type variable name index but it does not seem to
+ // // conform to the language specification.
+ // if (member != ElementType.TYPE)
+ parseDescriptor(signature);
+
+ if (last != null)
+ last.signature = signature;
+
+ if (cd != null)
+ cd.signature(signature);
+ }
+
+ /**
+ * Handle a constant value call the data collector with it
+ */
+ void doConstantValue(DataInputStream in) throws IOException {
+ int constantValue_index = in.readUnsignedShort();
+ if (cd == null)
+ return;
+
+ Object object = pool[constantValue_index];
+ if (object == null)
+ object = pool[intPool[constantValue_index]];
+
+ last.constant = object;
+ cd.constant(object);
+ }
+
+ /**
+ * <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 Exception
+ */
+ private void doCode(DataInputStream in) throws Exception {
+ /* int max_stack = */in.readUnsignedShort();
+ /* int max_locals = */in.readUnsignedShort();
+ int code_length = in.readInt();
+ byte code[] = new byte[code_length];
+ in.readFully(code);
+ crawl(code);
+ int exception_table_length = in.readUnsignedShort();
+ in.skipBytes(exception_table_length * 8);
+ doAttributes(in, ElementType.METHOD, false);
+ }
+
+ /**
+ * We must find Class.forName references ...
+ *
+ * @param code
+ */
+ protected void crawl(byte[] code) {
+ ByteBuffer bb = ByteBuffer.wrap(code);
+ bb.order(ByteOrder.BIG_ENDIAN);
+ int lastReference = -1;
+
+ while (bb.remaining() > 0) {
+ int instruction = 0xFF & bb.get();
+ switch (instruction) {
+ case OpCodes.ldc:
+ lastReference = 0xFF & bb.get();
+ break;
+
+ case OpCodes.ldc_w:
+ lastReference = 0xFFFF & bb.getShort();
+ break;
+
+ case OpCodes.invokespecial: {
+ int mref = 0xFFFF & bb.getShort();
+ if (cd != null)
+ getMethodDef(0, mref);
+ break;
+ }
+
+ case OpCodes.invokevirtual: {
+ int mref = 0xFFFF & bb.getShort();
+ if (cd != null)
+ getMethodDef(0, mref);
+ break;
+ }
+
+ case OpCodes.invokeinterface: {
+ int mref = 0xFFFF & bb.getShort();
+ if (cd != null)
+ getMethodDef(0, mref);
+ break;
+ }
+
+ case OpCodes.invokestatic: {
+ int methodref = 0xFFFF & bb.getShort();
+ if (cd != null)
+ getMethodDef(0, methodref);
+
+ if ((methodref == forName || methodref == class$) && lastReference != -1
+ && pool[intPool[lastReference]] instanceof String) {
+ String fqn = (String) pool[intPool[lastReference]];
+ if (!fqn.equals("class") && fqn.indexOf('.') > 0) {
+ TypeRef clazz = analyzer.getTypeRefFromFQN(fqn);
+ referTo(clazz);
+ }
+ lastReference = -1;
+ }
+ break;
+ }
+
+ case OpCodes.tableswitch:
+ // Skip to place divisible by 4
+ while ((bb.position() & 0x3) != 0)
+ bb.get();
+ /* int deflt = */
+ bb.getInt();
+ int low = bb.getInt();
+ int high = bb.getInt();
+ try {
+ bb.position(bb.position() + (high - low + 1) * 4);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ lastReference = -1;
+ break;
+
+ case OpCodes.lookupswitch:
+ // Skip to place divisible by 4
+ while ((bb.position() & 0x3) != 0)
+ bb.get();
+ /* deflt = */
+ bb.getInt();
+ int npairs = bb.getInt();
+ bb.position(bb.position() + npairs * 8);
+ lastReference = -1;
+ break;
+
+ default:
+ lastReference = -1;
+ bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
+ }
+ }
+ }
+
+ private void doSourceFile(DataInputStream in) throws IOException {
+ int sourcefile_index = in.readUnsignedShort();
+ this.sourceFile = pool[sourcefile_index].toString();
+ }
+
+ private void doParameterAnnotations(DataInputStream in, ElementType member,
+ RetentionPolicy policy) throws IOException {
+ int num_parameters = in.readUnsignedByte();
+ for (int p = 0; p < num_parameters; p++) {
+ if (cd != null)
+ cd.parameter(p);
+ doAnnotations(in, member, policy);
+ }
+ }
+
+ private void doAnnotations(DataInputStream in, ElementType member, RetentionPolicy policy)
+ throws IOException {
+ int num_annotations = in.readUnsignedShort(); // # of annotations
+ for (int a = 0; a < num_annotations; a++) {
+ if (cd == null)
+ doAnnotation(in, member, policy, false);
+ else {
+ Annotation annotion = doAnnotation(in, member, policy, true);
+ cd.annotation(annotion);
+ }
+ }
+ }
+
+ private Annotation doAnnotation(DataInputStream in, ElementType member, RetentionPolicy policy,
+ boolean collect) throws IOException {
+ int type_index = in.readUnsignedShort();
+ if (annotations == null)
+ annotations = new HashSet<TypeRef>();
+
+ TypeRef tr = analyzer.getTypeRef(pool[type_index].toString());
+ annotations.add(tr);
+
+ if (policy == RetentionPolicy.RUNTIME) {
+ descriptors.add(Integer.valueOf(type_index));
+ hasRuntimeAnnotations = true;
+ } else {
+ hasClassAnnotations = true;
+ }
+ TypeRef name = analyzer.getTypeRef((String) pool[type_index]);
+ int num_element_value_pairs = in.readUnsignedShort();
+ Map<String, Object> elements = null;
+ for (int v = 0; v < num_element_value_pairs; v++) {
+ int element_name_index = in.readUnsignedShort();
+ String element = (String) pool[element_name_index];
+ Object value = doElementValue(in, member, policy, collect);
+ if (collect) {
+ if (elements == null)
+ elements = new LinkedHashMap<String, Object>();
+ elements.put(element, value);
+ }
+ }
+ if (collect)
+ return new Annotation(name, elements, member, policy);
+ else
+ return null;
+ }
+
+ private Object doElementValue(DataInputStream in, ElementType member, RetentionPolicy policy,
+ boolean collect) throws IOException {
+ char tag = (char) in.readUnsignedByte();
+ switch (tag) {
+ case 'B': // Byte
+ case 'C': // Character
+ case 'I': // Integer
+ case 'S': // Short
+ int const_value_index = in.readUnsignedShort();
+ return intPool[const_value_index];
+
+ case 'D': // Double
+ case 'F': // Float
+ case 's': // String
+ case 'J': // Long
+ const_value_index = in.readUnsignedShort();
+ return pool[const_value_index];
+
+ case 'Z': // Boolean
+ const_value_index = in.readUnsignedShort();
+ return pool[const_value_index] == null || pool[const_value_index].equals(0) ? false
+ : true;
+
+ case 'e': // enum constant
+ int type_name_index = in.readUnsignedShort();
+ if (policy == RetentionPolicy.RUNTIME)
+ descriptors.add(Integer.valueOf(type_name_index));
+ int const_name_index = in.readUnsignedShort();
+ return pool[const_name_index];
+
+ case 'c': // Class
+ int class_info_index = in.readUnsignedShort();
+ if (policy == RetentionPolicy.RUNTIME)
+ descriptors.add(Integer.valueOf(class_info_index));
+ return pool[class_info_index];
+
+ case '@': // Annotation type
+ return doAnnotation(in, member, policy, collect);
+
+ case '[': // Array
+ int num_values = in.readUnsignedShort();
+ Object[] result = new Object[num_values];
+ for (int i = 0; i < num_values; i++) {
+ result[i] = doElementValue(in, member, policy, collect);
+ }
+ return result;
+
+ default:
+ throw new IllegalArgumentException("Invalid value for Annotation ElementValue tag "
+ + tag);
+ }
+ }
+
+ /**
+ * Add a new package reference.
+ *
+ * @param packageRef
+ * A '.' delimited package name
+ */
+ void referTo(TypeRef typeRef) {
+ if (xref != null)
+ xref.add(typeRef);
+ if (typeRef.isPrimitive())
+ return;
+
+ PackageRef packageRef = typeRef.getPackageRef();
+ if (packageRef.isPrimitivePackage())
+ return;
+
+ imports.add(packageRef);
+ }
+
+ /**
+ * This method parses a descriptor and adds the package of the descriptor to
+ * the referenced packages.
+ *
+ * The syntax of the descriptor is:
+ *
+ * <pre>
+ * descriptor ::= ( '(' reference * ')' )? reference
+ * reference ::= 'L' classname ( '<' references '>' )? ';' | 'B' | 'Z' | ... | '+' | '-' | '['
+ * </pre>
+ *
+ * This methods uses heavy recursion to parse the descriptor and a roving
+ * pointer to limit the creation of string objects.
+ *
+ * @param descriptor
+ * The to be parsed descriptor
+ * @param rover
+ * The pointer to start at
+ */
+ public void parseDescriptor(String descriptor) {
+ // Some descriptors are weird, they start with a generic
+ // declaration that contains ':', not sure what they mean ...
+ int rover = 0;
+ if (descriptor.charAt(0) == '<') {
+ rover = parseFormalTypeParameters(descriptor, rover);
+ }
+
+ if (descriptor.charAt(rover) == '(') {
+ rover = parseReferences(descriptor, rover + 1, ')');
+ rover++;
+ }
+ parseReferences(descriptor, rover, (char) 0);
+ }
+
+ /**
+ * Parse a sequence of references. A sequence ends with a given character or
+ * when the string ends.
+ *
+ * @param descriptor
+ * The whole descriptor.
+ * @param rover
+ * The index in the descriptor
+ * @param delimiter
+ * The end character or 0
+ * @return the last index processed, one character after the delimeter
+ */
+ int parseReferences(String descriptor, int rover, char delimiter) {
+ int r = rover;
+ while (r < descriptor.length() && descriptor.charAt(r) != delimiter) {
+ r = parseReference(descriptor, r);
+ }
+ return r;
+ }
+
+ /**
+ * Parse a single reference. This can be a single character or an object
+ * reference when it starts with 'L'.
+ *
+ * @param descriptor
+ * The descriptor
+ * @param rover
+ * The place to start
+ * @return The return index after the reference
+ */
+ int parseReference(String descriptor, int rover) {
+ int r = rover;
+ char c = descriptor.charAt(r);
+ while (c == '[')
+ c = descriptor.charAt(++r);
+
+ if (c == '<') {
+ r = parseReferences(descriptor, r + 1, '>');
+ } else if (c == 'T') {
+ // Type variable name
+ r++;
+ while (descriptor.charAt(r) != ';')
+ r++;
+ } else if (c == 'L') {
+ StringBuilder sb = new StringBuilder();
+ r++;
+ while ((c = descriptor.charAt(r)) != ';') {
+ if (c == '<') {
+ r = parseReferences(descriptor, r + 1, '>');
+ } else
+ sb.append(c);
+ r++;
+ }
+ TypeRef ref = analyzer.getTypeRef(sb.toString());
+ if (cd != null)
+ cd.addReference(ref);
+
+ referTo(ref);
+ } else {
+ if ("+-*BCDFIJSZV".indexOf(c) < 0)
+ ;// System.err.println("Should not skip: " + c);
+ }
+
+ // this skips a lot of characters
+ // [, *, +, -, B, etc.
+
+ return r + 1;
+ }
+
+ /**
+ * FormalTypeParameters
+ *
+ * @param descriptor
+ * @param index
+ * @return
+ */
+ private int parseFormalTypeParameters(String descriptor, int index) {
+ index++;
+ while (descriptor.charAt(index) != '>') {
+ // Skip IDENTIFIER
+ index = descriptor.indexOf(':', index) + 1;
+ if (index == 0)
+ throw new IllegalArgumentException("Expected IDENTIFIER: " + descriptor);
+
+ // ClassBound? InterfaceBounds
+
+ char c = descriptor.charAt(index);
+
+ // Class Bound?
+ if (c == 'L' || c == 'T') {
+ index = parseReference(descriptor, index); // class reference
+ c = descriptor.charAt(index);
+ }
+
+ // Interface Bounds
+ while (c == ':') {
+ index++;
+ index = parseReference(descriptor, index);
+ c = descriptor.charAt(index);
+ } // for each interface
+
+ } // for each formal parameter
+ return index + 1; // skip >
+ }
+
+ public Set<PackageRef> getReferred() {
+ return imports;
+ }
+
+ public String getAbsolutePath() {
+ return path;
+ }
+
+ public String getSourceFile() {
+ return sourceFile;
+ }
+
+ /**
+ * .class construct for different compilers
+ *
+ * sun 1.1 Detect static variable class$com$acme$MyClass 1.2 " 1.3 " 1.4 "
+ * 1.5 ldc_w (class) 1.6 "
+ *
+ * eclipse 1.1 class$0, ldc (string), invokestatic Class.forName 1.2 " 1.3 "
+ * 1.5 ldc (class) 1.6 "
+ *
+ * 1.5 and later is not an issue, sun pre 1.5 is easy to detect the static
+ * variable that decodes the class name. For eclipse, the class$0 gives away
+ * we have a reference encoded in a string.
+ * compilerversions/compilerversions.jar contains test versions of all
+ * versions/compilers.
+ */
+
+ public void reset() {
+ pool = null;
+ intPool = null;
+ xref = null;
+ classes = null;
+ descriptors = null;
+ }
+
+ public boolean is(QUERY query, Instruction instr, Analyzer analyzer) throws Exception {
+ switch (query) {
+ case ANY:
+ return true;
+
+ case NAMED:
+ if (instr.matches(getClassName().getDottedOnly()))
+ return !instr.isNegated();
+ return false;
+
+ case VERSION:
+ String v = major + "." + minor;
+ if (instr.matches(v))
+ return !instr.isNegated();
+ return false;
+
+ case IMPLEMENTS:
+ for (int i = 0; interfaces != null && i < interfaces.length; i++) {
+ if (instr.matches(interfaces[i].getDottedOnly()))
+ return !instr.isNegated();
+ }
+ break;
+
+ case EXTENDS:
+ if (zuper == null)
+ return false;
+
+ if (instr.matches(zuper.getDottedOnly()))
+ return !instr.isNegated();
+ break;
+
+ case PUBLIC:
+ return Modifier.isPublic(accessx);
+
+ case CONCRETE:
+ return !Modifier.isAbstract(accessx);
+
+ case ANNOTATED:
+ if (annotations == null)
+ return false;
+
+ for (TypeRef annotation : annotations) {
+ if (instr.matches(annotation.getFQN()))
+ return !instr.isNegated();
+ }
+
+ return false;
+
+ case RUNTIMEANNOTATIONS:
+ return hasClassAnnotations;
+ case CLASSANNOTATIONS:
+ return hasClassAnnotations;
+
+ case ABSTRACT:
+ return Modifier.isAbstract(accessx);
+
+ case IMPORTS:
+ for (PackageRef imp : imports) {
+ if (instr.matches(imp.getFQN()))
+ return !instr.isNegated();
+ }
+ }
+
+ if (zuper == null)
+ return false;
+
+ Clazz clazz = analyzer.findClass(zuper);
+ if (clazz == null)
+ return false;
+
+ return clazz.is(query, instr, analyzer);
+ }
+
+ public String toString() {
+ return className.getFQN();
+ }
+
+ /**
+ * Called when crawling the byte code and a method reference is found
+ *
+ */
+ void getMethodDef(int access, int methodRefPoolIndex) {
+ if ( methodRefPoolIndex == 0)
+ return;
+
+ Object o = pool[methodRefPoolIndex];
+ if (o != null && o instanceof Assoc) {
+ Assoc assoc = (Assoc) o;
+ if (assoc.tag == 10) {
+ int string_index = intPool[assoc.a];
+ TypeRef className = analyzer.getTypeRef((String) pool[string_index]);
+ int name_and_type_index = assoc.b;
+ Assoc name_and_type = (Assoc) pool[name_and_type_index];
+ if (name_and_type.tag == 12) {
+ // Name and Type
+ int name_index = name_and_type.a;
+ int type_index = name_and_type.b;
+ String method = (String) pool[name_index];
+ String descriptor = (String) pool[type_index];
+ cd.referenceMethod(access, className, method, descriptor);
+ } else
+ throw new IllegalArgumentException(
+ "Invalid class file (or parsing is wrong), assoc is not type + name (12)");
+ } else
+ throw new IllegalArgumentException(
+ "Invalid class file (or parsing is wrong), Assoc is not method ref! (10)");
+ } else
+ throw new IllegalArgumentException(
+ "Invalid class file (or parsing is wrong), Not an assoc at a method ref");
+ }
+
+ public boolean isPublic() {
+ return Modifier.isPublic(accessx);
+ }
+
+ public boolean isProtected() {
+ return Modifier.isProtected(accessx);
+ }
+
+ public boolean isEnum() {
+ return zuper != null && zuper.getBinary().equals("java/lang/Enum");
+ }
+
+ public JAVA getFormat() {
+ return JAVA.format(major);
+
+ }
+
+ public static String objectDescriptorToFQN(String string) {
+ if (string.startsWith("L") && string.endsWith(";"))
+ return string.substring(1, string.length() - 1).replace('/', '.');
+
+ switch (string.charAt(0)) {
+ case 'V':
+ return "void";
+ case 'B':
+ return "byte";
+ case 'C':
+ return "char";
+ case 'I':
+ return "int";
+ case 'S':
+ return "short";
+ case 'D':
+ return "double";
+ case 'F':
+ return "float";
+ case 'J':
+ return "long";
+ case 'Z':
+ return "boolean";
+ case '[': // Array
+ return objectDescriptorToFQN(string.substring(1)) + "[]";
+ }
+ throw new IllegalArgumentException("Invalid type character in descriptor " + string);
+ }
+
+ public static String unCamel(String id) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < id.length(); i++) {
+ char c = id.charAt(i);
+ if (c == '_' || c == '$' || c == '.') {
+ if (out.length() > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
+ out.append(' ');
+ continue;
+ }
+
+ int n = i;
+ while (n < id.length() && Character.isUpperCase(id.charAt(n))) {
+ n++;
+ }
+ if (n == i)
+ out.append(id.charAt(i));
+ else {
+ boolean tolower = (n - i) == 1;
+ if (i > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
+ out.append(' ');
+
+ for (; i < n;) {
+ if (tolower)
+ out.append(Character.toLowerCase(id.charAt(i)));
+ else
+ out.append(id.charAt(i));
+ i++;
+ }
+ i--;
+ }
+ }
+ if (id.startsWith("."))
+ out.append(" *");
+ out.replace(0, 1, Character.toUpperCase(out.charAt(0)) + "");
+ return out.toString();
+ }
+
+ public boolean isInterface() {
+ return Modifier.isInterface(accessx);
+ }
+
+ public boolean isAbstract() {
+ return Modifier.isAbstract(accessx);
+ }
+
+ public int getAccess() {
+ if (innerAccess == -1)
+ return accessx;
+ else
+ return innerAccess;
+ }
+
+ public TypeRef getClassName() {
+ return className;
+ }
+
+ /**
+ * To provide an enclosing instance
+ *
+ * @param access
+ * @param name
+ * @param descriptor
+ * @return
+ */
+ public MethodDef getMethodDef(int access, String name, String descriptor) {
+ return new MethodDef(access, name, descriptor);
+ }
+
+ public TypeRef getSuper() {
+ return zuper;
+ }
+
+ public String getFQN() {
+ return className.getFQN();
+ }
+
+ public TypeRef[] getInterfaces() {
+ return interfaces;
+ }
+
+ public void setInnerAccess(int access) {
+ innerAccess = access;
+ }
+
+ public boolean isFinal() {
+ return Modifier.isFinal(accessx);
+ }
+
+ public void setDeprecated(boolean b) {
+ deprecated = b;
+ }
+
+ public boolean isDeprecated() {
+ return deprecated;
+ }
+
+ public boolean isAnnotation() {
+ return (accessx & ACC_ANNOTATION) != 0;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/CombinedResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/CombinedResource.java
new file mode 100644
index 0000000..e8566db
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/CombinedResource.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) OSGi Alliance (2012). 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.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+
+public class CombinedResource extends WriteResource {
+ final List<Resource> resources = new ArrayList<Resource>();
+ long lastModified = 0;
+
+ @Override
+ public void write(final OutputStream out) throws IOException, Exception {
+ OutputStream unclosable = new FilterOutputStream(out) {
+ public void close() {
+ // Ignore
+ }
+ };
+ for ( Resource r : resources ) {
+ r.write(unclosable);
+ unclosable.flush();
+ }
+ }
+
+ @Override
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public void addResource(Resource r) {
+ lastModified = Math.max(lastModified, r.lastModified());
+ resources.add(r);
+ }
+
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/CommandResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/CommandResource.java
new file mode 100644
index 0000000..c9e3c8a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/CommandResource.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) OSGi Alliance (2012). 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.lib.osgi;
+
+import java.io.*;
+
+import aQute.libg.command.*;
+
+public class CommandResource extends WriteResource {
+ final long lastModified;
+ final Builder domain;
+ final String command;
+
+ public CommandResource(String command, Builder domain, long lastModified) {
+ this.lastModified = lastModified;
+ this.domain = domain;
+ this.command = command;
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException, Exception {
+ StringBuilder errors = new StringBuilder();
+ StringBuilder stdout = new StringBuilder();
+ try {
+ domain.trace("executing command %s", command);
+ Command cmd = new Command("sh -l");
+ cmd.inherit();
+ String oldpath = cmd.var("PATH");
+
+ String path = domain.getProperty("-PATH");
+ if (path != null) {
+ path = path.replaceAll("\\s*,\\s*",File.pathSeparator);
+ path = path.replaceAll("\\$\\{@\\}", oldpath);
+ cmd.var("PATH", path);
+ domain.trace("PATH: %s", path);
+ }
+ OutputStreamWriter osw = new OutputStreamWriter(out);
+ int result = cmd.execute(command,stdout, errors);
+ osw.append(stdout);
+ osw.flush();
+ if ( result != 0) {
+ domain.error("executing command failed %s %s", command, stdout + "\n" + errors);
+ }
+ } catch( Exception e) {
+ domain.error("executing command failed %s %s", command, e.getMessage());
+ }
+ }
+
+ @Override
+ public long lastModified() {
+ return lastModified;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java b/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java
new file mode 100644
index 0000000..b196a1c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java
@@ -0,0 +1,300 @@
+package aQute.lib.osgi;
+
+import java.nio.charset.*;
+import java.util.*;
+import java.util.regex.*;
+
+public interface Constants {
+ /*
+ * Defined in OSGi
+ */
+ /**
+ * @syntax Bundle-ActivationPolicy ::= policy ( ’;’ directive )* policy ::=
+ * ’lazy’
+ */
+ String BND_ADDXMLTOTEST = "Bnd-AddXMLToTest";
+ String BUNDLE_ACTIVATIONPOLICY = "Bundle-ActivationPolicy";
+ String BUNDLE_ACTIVATOR = "Bundle-Activator";
+ String BUNDLE_BLUEPRINT = "Bundle-Copyright";
+ String BUNDLE_CATEGORY = "Bundle-Category";
+ String BUNDLE_CLASSPATH = "Bundle-ClassPath";
+ String BUNDLE_CONTACTADDRESS = "Bundle-ContactAddress";
+ String BUNDLE_COPYRIGHT = "Bundle-Copyright";
+ String BUNDLE_DESCRIPTION = "Bundle-Description";
+ String BUNDLE_DOCURL = "Bundle-DocURL";
+ String BUNDLE_ICON = "Bundle-Icon";
+ String BUNDLE_LICENSE = "Bundle-License";
+ String BUNDLE_LOCALIZATION = "Bundle-Localization";
+ String BUNDLE_MANIFESTVERSION = "Bundle-ManifestVersion";
+ String BUNDLE_NAME = "Bundle-Name";
+ String BUNDLE_NATIVECODE = "Bundle-NativeCode";
+ String BUNDLE_REQUIREDEXECUTIONENVIRONMENT = "Bundle-RequiredExecutionEnvironment";
+ String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName";
+ String BUNDLE_UPDATELOCATION = "Bundle-UpdateLocation";
+ String BUNDLE_VENDOR = "Bundle-Vendor";
+ String BUNDLE_VERSION = "Bundle-Version";
+ String DYNAMICIMPORT_PACKAGE = "DynamicImport-Package";
+ String EXPORT_PACKAGE = "Export-Package";
+ String EXPORT_SERVICE = "Export-Service";
+ String FRAGMENT_HOST = "Fragment-Host";
+ String IMPORT_PACKAGE = "Import-Package";
+ String IMPORT_SERVICE = "Import-Service";
+ String PROVIDE_CAPABILITY = "Provide-Capability";
+ String REQUIRE_BUNDLE = "Require-Bundle";
+ String REQUIRE_CAPABILITY = "Require-Capability";
+ String SERVICE_COMPONENT = "Service-Component";
+
+ String PRIVATE_PACKAGE = "Private-Package";
+ String IGNORE_PACKAGE = "Ignore-Package";
+ String INCLUDE_RESOURCE = "Include-Resource";
+ String CONDITIONAL_PACKAGE = "Conditional-Package";
+ String BND_LASTMODIFIED = "Bnd-LastModified";
+ String CREATED_BY = "Created-By";
+ String TOOL = "Tool";
+ String TESTCASES = "Test-Cases";
+ String SIGNATURE_TEST = "-signaturetest";
+
+ String headers[] = {
+ BUNDLE_ACTIVATOR, BUNDLE_CONTACTADDRESS, BUNDLE_COPYRIGHT, BUNDLE_DESCRIPTION,
+ BUNDLE_DOCURL, BUNDLE_LOCALIZATION, BUNDLE_NATIVECODE, BUNDLE_VENDOR, BUNDLE_VERSION,
+ BUNDLE_LICENSE, BUNDLE_CLASSPATH, SERVICE_COMPONENT, EXPORT_PACKAGE, IMPORT_PACKAGE,
+ BUNDLE_LOCALIZATION, BUNDLE_MANIFESTVERSION, BUNDLE_NAME, BUNDLE_NATIVECODE,
+ BUNDLE_REQUIREDEXECUTIONENVIRONMENT, BUNDLE_SYMBOLICNAME, BUNDLE_VERSION,
+ FRAGMENT_HOST, PRIVATE_PACKAGE, IGNORE_PACKAGE, INCLUDE_RESOURCE, REQUIRE_BUNDLE,
+ IMPORT_SERVICE, EXPORT_SERVICE, CONDITIONAL_PACKAGE, BND_LASTMODIFIED, TESTCASES,
+ SIGNATURE_TEST, REQUIRE_CAPABILITY, PROVIDE_CAPABILITY };
+
+ String BUILDPATH = "-buildpath";
+ String BUILDPACKAGES = "-buildpackages";
+ String BUMPPOLICY = "-bumppolicy";
+ String CONDUIT = "-conduit";
+ String COMPILER_SOURCE = "-source";
+ String COMPILER_TARGET = "-target";
+ String DEPENDSON = "-dependson";
+ String DEPLOY = "-deploy";
+ String DEPLOYREPO = "-deployrepo";
+ String DIGESTS = "-digests";
+ String DONOTCOPY = "-donotcopy";
+ String DEBUG = "-debug";
+ String EXPORT_CONTENTS = "-exportcontents";
+ String FAIL_OK = "-failok";
+ String INCLUDE = "-include";
+ String INCLUDERESOURCE = "-includeresource";
+ String MAKE = "-make";
+ String METATYPE = "-metatype";
+ String MANIFEST = "-manifest";
+ String SAVEMANIFEST = "-savemanifest";
+ String NAMESECTION = "-namesection";
+ String NODEFAULTVERSION = "-nodefaultversion";
+ String NOEXTRAHEADERS = "-noextraheaders";
+ String NOMANIFEST = "-nomanifest";
+ String NOUSES = "-nouses";
+ @Deprecated String NOPE = "-nope";
+ String NOBUNDLES = "-nobundles";
+ String PEDANTIC = "-pedantic";
+ String PLUGIN = "-plugin";
+ String PLUGINPATH = "-pluginpath";
+ String POM = "-pom";
+ String RELEASEREPO = "-releaserepo";
+ String REMOVEHEADERS = "-removeheaders";
+ String RESOURCEONLY = "-resourceonly";
+ String SOURCES = "-sources";
+ String SOURCEPATH = "-sourcepath";
+ String SUB = "-sub";
+ String RUNPROPERTIES = "-runproperties";
+ String RUNSYSTEMPACKAGES = "-runsystempackages";
+ String RUNBUNDLES = "-runbundles";
+ String RUNPATH = "-runpath";
+ String RUNSTORAGE = "-runstorage";
+ String RUNBUILDS = "-runbuilds";
+ String RUNPATH_MAIN_DIRECTIVE = "main:";
+ String RUNPATH_LAUNCHER_DIRECTIVE = "launcher:";
+ String RUNVM = "-runvm";
+ String RUNTRACE = "-runtrace";
+ String RUNFRAMEWORK = "-runframework";
+ String RUNTIMEOUT = "-runtimeout";
+ String SNAPSHOT = "-snapshot";
+ String RUNFRAMEWORK_SERVICES = "services";
+ String RUNFRAMEWORK_NONE = "none";
+ String REPORTNEWER = "-reportnewer";
+ String SIGN = "-sign";
+ String TESTPACKAGES = "-testpackages";
+ String TESTREPORT = "-testreport";
+ String TESTPATH = "-testpath";
+ String TESTCONTINUOUS = "-testcontinuous";
+ String UNDERTEST = "-undertest";
+ String VERBOSE = "-verbose";
+ @Deprecated String VERSIONPOLICY_IMPL = "-versionpolicy-impl";
+ @Deprecated String VERSIONPOLICY_USES = "-versionpolicy-uses";
+ String PROVIDER_POLICY = "-provider-policy";
+ String CONSUMER_POLICY = "-consumer-policy";
+ @Deprecated String VERSIONPOLICY = "-versionpolicy";
+ String WAB = "-wab";
+ String WABLIB = "-wablib";
+ String REQUIRE_BND = "-require-bnd";
+
+ // Deprecated
+ String CLASSPATH = "-classpath";
+ String OUTPUT = "-output";
+
+ String options[] = { BUILDPATH,
+ BUMPPOLICY, CONDUIT, CLASSPATH, CONSUMER_POLICY, DEPENDSON, DONOTCOPY, EXPORT_CONTENTS,
+ FAIL_OK, INCLUDE, INCLUDERESOURCE, MAKE, MANIFEST, NOEXTRAHEADERS, NOUSES, NOBUNDLES,
+ PEDANTIC, PLUGIN, POM, PROVIDER_POLICY, REMOVEHEADERS, RESOURCEONLY, SOURCES,
+ SOURCEPATH, SOURCES, SOURCEPATH, SUB, RUNBUNDLES, RUNPATH, RUNSYSTEMPACKAGES,
+ RUNPROPERTIES, REPORTNEWER, UNDERTEST, TESTPATH, TESTPACKAGES, TESTREPORT, VERBOSE,
+ NOMANIFEST, DEPLOYREPO, RELEASEREPO, SAVEMANIFEST, RUNVM, WAB, WABLIB, RUNFRAMEWORK,
+ RUNTRACE, TESTCONTINUOUS, SNAPSHOT, NAMESECTION, DIGESTS };
+
+ // Ignore bundle specific headers. These bundles do not make
+ // a lot of sense to inherit
+ String[] BUNDLE_SPECIFIC_HEADERS = new String[] {
+ INCLUDE_RESOURCE, BUNDLE_ACTIVATOR, BUNDLE_CLASSPATH, BUNDLE_NAME, BUNDLE_NATIVECODE,
+ BUNDLE_SYMBOLICNAME, IMPORT_PACKAGE, EXPORT_PACKAGE, DYNAMICIMPORT_PACKAGE,
+ FRAGMENT_HOST, REQUIRE_BUNDLE, PRIVATE_PACKAGE, EXPORT_CONTENTS, TESTCASES, NOMANIFEST,
+ SIGNATURE_TEST, WAB, WABLIB, REQUIRE_CAPABILITY, PROVIDE_CAPABILITY };
+
+ char DUPLICATE_MARKER = '~';
+ String SPECIFICATION_VERSION = "specification-version";
+ String SPLIT_PACKAGE_DIRECTIVE = "-split-package:";
+ String IMPORT_DIRECTIVE = "-import:";
+ String NO_IMPORT_DIRECTIVE = "-noimport:";
+ String REMOVE_ATTRIBUTE_DIRECTIVE = "-remove-attribute:";
+ String LIB_DIRECTIVE = "lib:";
+ String NOANNOTATIONS = "-noannotations";
+ String COMMAND_DIRECTIVE = "command:";
+ String USES_DIRECTIVE = "uses:";
+ String MANDATORY_DIRECTIVE = "mandatory:";
+ String INCLUDE_DIRECTIVE = "include:";
+ String PROVIDE_DIRECTIVE = "provide:";
+ String EXCLUDE_DIRECTIVE = "exclude:";
+ String PRESENCE_DIRECTIVE = "presence:";
+ String PRIVATE_DIRECTIVE = "private:";
+ String SINGLETON_DIRECTIVE = "singleton:";
+ String EXTENSION_DIRECTIVE = "extension:";
+ String VISIBILITY_DIRECTIVE = "visibility:";
+ String FRAGMENT_ATTACHMENT_DIRECTIVE = "fragment-attachment:";
+ String RESOLUTION_DIRECTIVE = "resolution:";
+ String PATH_DIRECTIVE = "path:";
+ String SIZE_ATTRIBUTE = "size";
+ String LINK_ATTRIBUTE = "link";
+ String NAME_ATTRIBUTE = "name";
+ String DESCRIPTION_ATTRIBUTE = "description";
+ String OSNAME_ATTRIBUTE = "osname";
+ String OSVERSION_ATTRIBUTE = "osversion";
+ String PROCESSOR_ATTRIBUTE = "processor";
+ String LANGUAGE_ATTRIBUTE = "language";
+ String SELECTION_FILTER_ATTRIBUTE = "selection-filter";
+ String BLUEPRINT_WAIT_FOR_DEPENDENCIES_ATTRIBUTE = "blueprint.wait-for-dependencies";
+ String BLUEPRINT_TIMEOUT_ATTRIBUTE = "blueprint.timeout";
+ String VERSION_ATTRIBUTE = "version";
+ String BUNDLE_SYMBOLIC_NAME_ATTRIBUTE = "bundle-symbolic-name";
+ String BUNDLE_VERSION_ATTRIBUTE = "bundle-version";
+ String FROM_DIRECTIVE = "from:";
+
+ String KEYSTORE_LOCATION_DIRECTIVE = "keystore:";
+ String KEYSTORE_PROVIDER_DIRECTIVE = "provider:";
+ String KEYSTORE_PASSWORD_DIRECTIVE = "password:";
+ String SIGN_PASSWORD_DIRECTIVE = "sign-password:";
+
+ String NONE = "none";
+
+ String directives[] = {
+ SPLIT_PACKAGE_DIRECTIVE, NO_IMPORT_DIRECTIVE, IMPORT_DIRECTIVE, RESOLUTION_DIRECTIVE,
+ INCLUDE_DIRECTIVE, USES_DIRECTIVE, EXCLUDE_DIRECTIVE, KEYSTORE_LOCATION_DIRECTIVE,
+ KEYSTORE_PROVIDER_DIRECTIVE, KEYSTORE_PASSWORD_DIRECTIVE, SIGN_PASSWORD_DIRECTIVE,
+ COMMAND_DIRECTIVE, NOANNOTATIONS, LIB_DIRECTIVE, RUNPATH_LAUNCHER_DIRECTIVE,
+ FROM_DIRECTIVE, PRIVATE_DIRECTIVE
+
+ // TODO
+ };
+
+ String USES_USES = "<<USES>>";
+ String CURRENT_USES = "@uses";
+ String IMPORT_REFERENCE = "reference";
+ String IMPORT_PRIVATE = "private";
+ String[] importDirectives = {
+ IMPORT_REFERENCE, IMPORT_PRIVATE };
+
+ static final Pattern VALID_PROPERTY_TYPES = Pattern
+ .compile("(String|Long|Double|Float|Integer|Byte|Character|Boolean|Short)");
+
+ String DEFAULT_BND_EXTENSION = ".bnd";
+ String DEFAULT_JAR_EXTENSION = ".jar";
+ String DEFAULT_BAR_EXTENSION = ".bar";
+ String DEFAULT_BNDRUN_EXTENSION = ".bndrun";
+ String[] METAPACKAGES = { "META-INF",
+ "OSGI-INF", "OSGI-OPT" };
+
+ String CURRENT_VERSION = "@";
+ String CURRENT_PACKAGE = "@package";
+
+ String BUILDFILES = "buildfiles";
+
+ String EMPTY_HEADER = "<<EMPTY>>";
+
+ String EMBEDDED_REPO = "/embedded-repo.jar";
+ String LAUNCHER_PLUGIN = "Launcher-Plugin";
+ String TESTER_PLUGIN = "Tester-Plugin";
+
+ String DEFAULT_LAUNCHER_BSN = "biz.aQute.launcher";
+ String DEFAULT_TESTER_BSN = "biz.aQute.junit";
+
+ String DEFAULT_DO_NOT_COPY = "CVS|\\.svn|\\.git|\\.DS_Store";
+
+ Charset DEFAULT_CHARSET = Charset
+ .forName("UTF8");
+ String VERSION_FILTER = "version";
+ String PROVIDER_TYPE_DIRECTIVE = "x-provider-type:";
+
+ /**
+ * Component constants
+ */
+ public final static String NAMESPACE_STEM = "http://www.osgi.org/xmlns/scr";
+ public final static String JIDENTIFIER = "<<identifier>>";
+ public final static String COMPONENT_NAME = "name:";
+ public final static String COMPONENT_FACTORY = "factory:";
+ public final static String COMPONENT_SERVICEFACTORY = "servicefactory:";
+ public final static String COMPONENT_IMMEDIATE = "immediate:";
+ public final static String COMPONENT_ENABLED = "enabled:";
+ public final static String COMPONENT_DYNAMIC = "dynamic:";
+ public final static String COMPONENT_MULTIPLE = "multiple:";
+ public final static String COMPONENT_PROVIDE = "provide:";
+ public final static String COMPONENT_OPTIONAL = "optional:";
+ public final static String COMPONENT_PROPERTIES = "properties:";
+ public final static String COMPONENT_IMPLEMENTATION = "implementation:";
+ public final static String COMPONENT_DESIGNATE = "designate:";
+ public final static String COMPONENT_DESIGNATEFACTORY = "designateFactory:";
+ public final static String COMPONENT_DESCRIPTORS = ".descriptors:";
+
+ // v1.1.0
+ public final static String COMPONENT_VERSION = "version:";
+ public final static String COMPONENT_CONFIGURATION_POLICY = "configuration-policy:";
+ public final static String COMPONENT_MODIFIED = "modified:";
+ public final static String COMPONENT_ACTIVATE = "activate:";
+ public final static String COMPONENT_DEACTIVATE = "deactivate:";
+
+ final static Map<String, String> EMPTY = Collections
+ .emptyMap();
+
+ public final static String[] componentDirectives = new String[] {
+ COMPONENT_FACTORY, COMPONENT_IMMEDIATE, COMPONENT_ENABLED, COMPONENT_DYNAMIC,
+ COMPONENT_MULTIPLE, COMPONENT_PROVIDE, COMPONENT_OPTIONAL, COMPONENT_PROPERTIES,
+ COMPONENT_IMPLEMENTATION, COMPONENT_SERVICEFACTORY, COMPONENT_VERSION,
+ COMPONENT_CONFIGURATION_POLICY, COMPONENT_MODIFIED, COMPONENT_ACTIVATE,
+ COMPONENT_DEACTIVATE, COMPONENT_NAME, COMPONENT_DESCRIPTORS, COMPONENT_DESIGNATE,
+ COMPONENT_DESIGNATEFACTORY };
+
+ public final static Set<String> SET_COMPONENT_DIRECTIVES = new HashSet<String>(
+ Arrays.asList(componentDirectives));
+
+ public final static Set<String> SET_COMPONENT_DIRECTIVES_1_1 = //
+ new HashSet<String>(
+ Arrays.asList(
+ COMPONENT_VERSION,
+ COMPONENT_CONFIGURATION_POLICY,
+ COMPONENT_MODIFIED,
+ COMPONENT_ACTIVATE,
+ COMPONENT_DEACTIVATE));
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Descriptors.java b/bundleplugin/src/main/java/aQute/lib/osgi/Descriptors.java
new file mode 100644
index 0000000..366ca57
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Descriptors.java
@@ -0,0 +1,548 @@
+package aQute.lib.osgi;
+
+import java.util.*;
+
+import aQute.libg.generics.*;
+
+public class Descriptors {
+ Map<String, TypeRef> typeRefCache = Create.map();
+ Map<String, Descriptor> descriptorCache = Create.map();
+ Map<String, PackageRef> packageCache = Create.map();
+
+ // MUST BE BEFORE PRIMITIVES, THEY USE THE DEFAULT PACKAGE!!
+ final static PackageRef DEFAULT_PACKAGE = new PackageRef();
+ final static PackageRef PRIMITIVE_PACKAGE = new PackageRef();
+
+ final static TypeRef VOID = new ConcreteRef("V", "void", PRIMITIVE_PACKAGE);
+ final static TypeRef BOOLEAN = new ConcreteRef("Z", "boolean", PRIMITIVE_PACKAGE);
+ final static TypeRef BYTE = new ConcreteRef("B", "byte", PRIMITIVE_PACKAGE);
+ final static TypeRef CHAR = new ConcreteRef("C", "char", PRIMITIVE_PACKAGE);
+ final static TypeRef SHORT = new ConcreteRef("S", "short", PRIMITIVE_PACKAGE);
+ final static TypeRef INTEGER = new ConcreteRef("I", "int", PRIMITIVE_PACKAGE);
+ final static TypeRef LONG = new ConcreteRef("J", "long", PRIMITIVE_PACKAGE);
+ final static TypeRef DOUBLE = new ConcreteRef("D", "double", PRIMITIVE_PACKAGE);
+ final static TypeRef FLOAT = new ConcreteRef("F", "float", PRIMITIVE_PACKAGE);
+
+
+ {
+ packageCache.put("", DEFAULT_PACKAGE);
+ }
+
+ public interface TypeRef extends Comparable<TypeRef>{
+ String getBinary();
+
+ String getFQN();
+
+ String getPath();
+
+ boolean isPrimitive();
+
+ TypeRef getComponentTypeRef();
+
+ TypeRef getClassRef();
+
+ PackageRef getPackageRef();
+
+ String getShortName();
+
+ boolean isJava();
+
+ boolean isObject();
+
+ String getSourcePath();
+
+ String getDottedOnly();
+
+ }
+
+ public static class PackageRef implements Comparable<PackageRef>{
+ final String binaryName;
+ final String fqn;
+ final boolean java;
+
+ private PackageRef(String binaryName) {
+ this.binaryName = fqnToBinary(binaryName);
+ this.fqn = binaryToFQN(binaryName);
+ this.java = this.fqn.startsWith("java.") ; // && !this.fqn.equals("java.sql)"
+
+ // For some reason I excluded java.sql but the classloader will
+ // delegate anyway. So lost the understanding why I did it??
+ }
+
+ private PackageRef() {
+ this.binaryName = "";
+ this.fqn=".";
+ this.java = false;
+ }
+
+ public PackageRef getDuplicate() {
+ return new PackageRef(binaryName+Constants.DUPLICATE_MARKER);
+ }
+ public String getFQN() {
+ return fqn;
+ }
+
+ public String getBinary() {
+ return binaryName;
+ }
+
+ public String getPath() {
+ return binaryName;
+ }
+
+ public boolean isJava() {
+ return java;
+ }
+
+ public String toString() {
+ return fqn;
+ }
+
+ boolean isDefaultPackage() {
+ return this.fqn.equals(".");
+ }
+
+ boolean isPrimitivePackage() {
+ return this == PRIMITIVE_PACKAGE;
+ }
+
+ public int compareTo(PackageRef other) {
+ return fqn.compareTo(other.fqn);
+ }
+
+ public boolean equals(Object o) {
+ assert o instanceof PackageRef;
+ return o == this;
+ }
+
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ /**
+ * Decide if the package is a metadata package.
+ *
+ * @param pack
+ * @return
+ */
+ public boolean isMetaData() {
+ if (isDefaultPackage())
+ return true;
+
+ for (int i = 0; i < Constants.METAPACKAGES.length; i++) {
+ if (fqn.startsWith(Constants.METAPACKAGES[i]))
+ return true;
+ }
+ return false;
+ }
+
+ }
+
+ // We "intern" the
+ private static class ConcreteRef implements TypeRef {
+ final String binaryName;
+ final String fqn;
+ final boolean primitive;
+ final PackageRef packageRef;
+
+ ConcreteRef(PackageRef packageRef, String binaryName) {
+ if ( packageRef.getFQN().length() < 2 )
+ System.err.println("in default pack? " + binaryName);
+ this.binaryName = binaryName;
+ this.fqn = binaryToFQN(binaryName);
+ this.primitive = false;
+ this.packageRef = packageRef;
+ }
+
+ ConcreteRef(String binaryName, String fqn, PackageRef pref) {
+ this.binaryName = binaryName;
+ this.fqn = fqn;
+ this.primitive = true;
+ this.packageRef = pref;
+ }
+
+ public String getBinary() {
+ return binaryName;
+ }
+
+ public String getPath() {
+ return binaryName + ".class";
+ }
+
+ public String getSourcePath() {
+ return binaryName + ".java";
+ }
+
+ public String getFQN() {
+ return fqn;
+ }
+
+ public String getDottedOnly() {
+ return fqn.replace('$', '.');
+ }
+
+ public boolean isPrimitive() {
+ return primitive;
+ }
+
+ public TypeRef getComponentTypeRef() {
+ return null;
+ }
+
+ public TypeRef getClassRef() {
+ return this;
+ }
+
+ public PackageRef getPackageRef() {
+ return packageRef;
+ }
+
+ public String getShortName() {
+ int n = binaryName.lastIndexOf('/');
+ return binaryName.substring(n + 1);
+ }
+
+ public boolean isJava() {
+ return packageRef.isJava();
+ }
+
+ public String toString() {
+ return fqn;
+ }
+
+ public boolean isObject() {
+ return fqn.equals("java.lang.Object");
+ }
+
+ public boolean equals(Object other) {
+ assert other instanceof TypeRef;
+ return this == other;
+ }
+
+ public int compareTo(TypeRef other) {
+ if ( this == other)
+ return 0;
+ return fqn.compareTo(other.getFQN());
+ }
+
+ }
+
+ private static class ArrayRef implements TypeRef {
+ final TypeRef component;
+
+ ArrayRef(TypeRef component) {
+ this.component = component;
+ }
+
+ public String getBinary() {
+ return "[" + component.getBinary();
+ }
+
+ public String getFQN() {
+ return component.getFQN() + "[]";
+ }
+
+ public String getPath() {
+ return component.getPath();
+ }
+
+ public String getSourcePath() {
+ return component.getSourcePath();
+ }
+
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ public TypeRef getComponentTypeRef() {
+ return component;
+ }
+
+ public TypeRef getClassRef() {
+ return component.getClassRef();
+ }
+
+ public boolean equals(Object other) {
+ if (other == null || other.getClass() != getClass())
+ return false;
+
+ return component.equals(((ArrayRef) other).component);
+ }
+
+ public PackageRef getPackageRef() {
+ return component.getPackageRef();
+ }
+
+ public String getShortName() {
+ return component.getShortName() + "[]";
+ }
+
+ public boolean isJava() {
+ return component.isJava();
+ }
+
+ public String toString() {
+ return component.toString() + "[]";
+ }
+
+ public boolean isObject() {
+ return false;
+ }
+
+ public String getDottedOnly() {
+ return component.getDottedOnly();
+ }
+
+ public int compareTo(TypeRef other) {
+ if ( this == other)
+ return 0;
+
+ return getFQN().compareTo(other.getFQN());
+ }
+
+ }
+
+ public TypeRef getTypeRef(String binaryClassName) {
+ assert !binaryClassName.endsWith(".class");
+
+ TypeRef ref = typeRefCache.get(binaryClassName);
+ if (ref != null)
+ return ref;
+
+ if (binaryClassName.startsWith("[")) {
+ ref = getTypeRef(binaryClassName.substring(1));
+ ref = new ArrayRef(ref);
+ } else {
+ if (binaryClassName.length() >= 1) {
+ switch (binaryClassName.charAt(0)) {
+ case 'V':
+ return VOID;
+ case 'B':
+ return BYTE;
+ case 'C':
+ return CHAR;
+ case 'I':
+ return INTEGER;
+ case 'S':
+ return SHORT;
+ case 'D':
+ return DOUBLE;
+ case 'F':
+ return FLOAT;
+ case 'J':
+ return LONG;
+ case 'Z':
+ return BOOLEAN;
+ case 'L':
+ binaryClassName = binaryClassName.substring(1, binaryClassName.length() - 1);
+ break;
+ }
+ // falls trough for other 1 letter class names
+ }
+ ref = typeRefCache.get(binaryClassName);
+ if ( ref != null)
+ return ref;
+
+ PackageRef pref;
+ int n = binaryClassName.lastIndexOf('/');
+ if (n < 0)
+ pref = DEFAULT_PACKAGE;
+ else
+ pref = getPackageRef(binaryClassName.substring(0, n));
+
+ ref = new ConcreteRef(pref, binaryClassName);
+ }
+
+ typeRefCache.put(binaryClassName, ref);
+ return ref;
+ }
+
+ public PackageRef getPackageRef(String binaryPackName) {
+ if (binaryPackName.indexOf('.') >= 0 ) {
+ binaryPackName = binaryPackName.replace('.', '/');
+ }
+ PackageRef ref = packageCache.get(binaryPackName);
+ if (ref != null)
+ return ref;
+
+ ref = new PackageRef(binaryPackName);
+ packageCache.put(binaryPackName, ref);
+ return ref;
+ }
+
+ public Descriptor getDescriptor(String descriptor) {
+ Descriptor d = descriptorCache.get(descriptor);
+ if (d != null)
+ return d;
+ d = new Descriptor(descriptor);
+ descriptorCache.put(descriptor, d);
+ return d;
+ }
+
+ public class Descriptor {
+ final TypeRef type;
+ final TypeRef[] prototype;
+ final String descriptor;
+
+ private Descriptor(String descriptor) {
+ this.descriptor = descriptor;
+ int index = 0;
+ List<TypeRef> types = Create.list();
+ if (descriptor.charAt(index) == '(') {
+ index++;
+ while (descriptor.charAt(index) != ')') {
+ index = parse(types, descriptor, index);
+ }
+ index++; // skip )
+ prototype = types.toArray(new TypeRef[types.size()]);
+ types.clear();
+ } else
+ prototype = null;
+
+ index = parse(types, descriptor, index);
+ type = types.get(0);
+ }
+
+ int parse(List<TypeRef> types, String descriptor, int index) {
+ char c;
+ StringBuilder sb = new StringBuilder();
+ while ((c = descriptor.charAt(index++)) == '[') {
+ sb.append('[');
+ }
+
+ switch (c) {
+ case 'L':
+ while ((c = descriptor.charAt(index++)) != ';') {
+ // TODO
+ sb.append(c);
+ }
+ break;
+
+ case 'V':
+ case 'B':
+ case 'C':
+ case 'I':
+ case 'S':
+ case 'D':
+ case 'F':
+ case 'J':
+ case 'Z':
+ sb.append(c);
+ break;
+
+ default:
+ throw new IllegalArgumentException("Invalid type in descriptor: " + c + " from "
+ + descriptor + "[" + index + "]");
+ }
+ types.add(getTypeRef(sb.toString()));
+ return index;
+ }
+
+ public TypeRef getType() {
+ return type;
+ }
+
+ public TypeRef[] getPrototype() {
+ return prototype;
+ }
+
+ public boolean equals(Object other) {
+ if (other == null || other.getClass() != getClass())
+ return false;
+
+ return Arrays.equals(prototype, ((Descriptor) other).prototype)
+ && type == ((Descriptor) other).type;
+ }
+
+ public int hashCode() {
+ return prototype == null ? type.hashCode() : type.hashCode()
+ ^ Arrays.hashCode(prototype);
+ }
+
+ public String toString() {
+ return descriptor;
+ }
+ }
+
+ /**
+ * Return the short name of a FQN
+ */
+
+ public static String getShortName(String fqn) {
+ assert fqn.indexOf('/') < 0;
+
+ int n = fqn.lastIndexOf('.');
+ if (n >= 0) {
+ return fqn.substring(n + 1);
+ }
+ return fqn;
+ }
+
+ public static String binaryToFQN(String binary) {
+ StringBuilder sb = new StringBuilder();
+ for ( int i=0, l=binary.length(); i<l; i++) {
+ char c = binary.charAt(i);
+
+ if ( c == '/')
+ sb.append('.');
+ else
+ sb.append(c);
+ }
+ String result = sb.toString();
+ assert result.length() > 0;
+ return result;
+ }
+
+ public static String fqnToBinary(String binary) {
+ return binary.replace('.', '/');
+ }
+
+ public static String getPackage(String binaryNameOrFqn) {
+ int n = binaryNameOrFqn.lastIndexOf('/');
+ if (n >= 0)
+ return binaryNameOrFqn.substring(0, n).replace('/', '.');
+
+ n = binaryNameOrFqn.lastIndexOf(".");
+ if (n >= 0)
+ return binaryNameOrFqn.substring(0, n);
+
+ return ".";
+ }
+
+ public static String fqnToPath(String s) {
+ return fqnToBinary(s) + ".class";
+ }
+
+ public TypeRef getTypeRefFromFQN(String fqn) {
+ if ( fqn.equals("boolean"))
+ return BOOLEAN;
+
+ if ( fqn.equals("byte"))
+ return BOOLEAN;
+
+ if ( fqn.equals("char"))
+ return CHAR;
+
+ if ( fqn.equals("short"))
+ return SHORT;
+
+ if ( fqn.equals("int"))
+ return INTEGER;
+
+ if ( fqn.equals("long"))
+ return LONG;
+
+ if ( fqn.equals("float"))
+ return FLOAT;
+
+ if ( fqn.equals("double"))
+ return DOUBLE;
+
+ return getTypeRef(fqnToBinary(fqn));
+ }
+
+ public TypeRef getTypeRefFromPath(String path) {
+ assert path.endsWith(".class");
+ return getTypeRef(path.substring(0,path.length()-6));
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Domain.java b/bundleplugin/src/main/java/aQute/lib/osgi/Domain.java
new file mode 100644
index 0000000..b6ef379
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Domain.java
@@ -0,0 +1,256 @@
+package aQute.lib.osgi;
+
+import static aQute.lib.osgi.Constants.*;
+
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+/**
+ * This class abstracts domains that have properties holding OSGi meta data. It
+ * provides access to the keys, the set method and the get method. It then
+ * provides convenient methods to access these properties via semantic methods.
+ *
+ */
+public abstract class Domain implements Iterable<String> {
+
+ public abstract String get(String key);
+
+ public String get(String key, String deflt) {
+ String result = get(key);
+ if (result != null)
+ return result;
+ return deflt;
+ }
+
+ public abstract void set(String key, String value);
+
+ public abstract Iterator<String> iterator();
+
+ public static Domain domain(final Manifest manifest) {
+ Attributes attrs = manifest.getMainAttributes();
+ return domain(attrs);
+ }
+
+ public static Domain domain(final Attributes attrs) {
+ return new Domain() {
+
+ @Override public String get(String key) {
+ return attrs.getValue(key);
+ }
+
+ @Override public void set(String key, String value) {
+ attrs.putValue(key, value);
+ }
+
+ @Override public Iterator<String> iterator() {
+ final Iterator<Object> it = attrs.keySet().iterator();
+
+ return new Iterator<String>() {
+
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public String next() {
+ return it.next().toString();
+ }
+
+ public void remove() {
+ it.remove();
+ }
+ };
+ }
+ };
+ }
+
+ public static Domain domain(final Processor processor) {
+ return new Domain() {
+
+ @Override public String get(String key) {
+ return processor.getProperty(key);
+ }
+
+ @Override public String get(String key, String deflt) {
+ return processor.getProperty(key, deflt);
+ }
+
+ @Override public void set(String key, String value) {
+ processor.setProperty(key, value);
+ }
+
+ @Override public Iterator<String> iterator() {
+ final Iterator<String> it = processor.getPropertyKeys(true).iterator();
+
+ return new Iterator<String>() {
+ String current;
+
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public String next() {
+ return current = it.next().toString();
+ }
+
+ public void remove() {
+ processor.getProperties().remove(current);
+ }
+ };
+ }
+ };
+ }
+
+ public static Domain domain(final Map<String, String> map) {
+ return new Domain() {
+
+ @Override public String get(String key) {
+ return map.get(key);
+ }
+
+ @Override public void set(String key, String value) {
+ map.put(key, value);
+ }
+
+ @Override public Iterator<String> iterator() {
+ return map.keySet().iterator();
+ }
+ };
+ }
+
+ public Parameters getParameters(String key, Reporter reporter) {
+ return new Parameters(get(key), reporter);
+ }
+
+ public Parameters getParameters(String key) {
+ return new Parameters(get(key));
+ }
+
+ public Parameters getParameters(String key, String deflt) {
+ return new Parameters(get(key, deflt));
+ }
+
+ public Parameters getParameters(String key, String deflt, Reporter reporter) {
+ return new Parameters(get(key, deflt), reporter);
+ }
+
+ public Parameters getImportPackage() {
+ return getParameters(IMPORT_PACKAGE);
+ }
+
+ public Parameters getExportPackage() {
+ return getParameters(EXPORT_PACKAGE);
+ }
+
+ public Parameters getBundleClassPath() {
+ return getParameters(BUNDLE_CLASSPATH);
+ }
+
+ public Parameters getPrivatePackage() {
+ return getParameters(PRIVATE_PACKAGE);
+ }
+
+ public Parameters getIncludeResource() {
+ Parameters ic = getParameters(INCLUDE_RESOURCE);
+ ic.putAll( getParameters(INCLUDERESOURCE));
+ return ic;
+ }
+
+ public Parameters getDynamicImportPackage() {
+ return getParameters(DYNAMICIMPORT_PACKAGE);
+ }
+
+ public Parameters getExportContents() {
+ return getParameters(EXPORT_CONTENTS);
+ }
+
+ public String getBundleActivator() {
+ return get(BUNDLE_ACTIVATOR);
+ }
+
+ public void setPrivatePackage(String s) {
+ if (s != null)
+ set(PRIVATE_PACKAGE, s);
+ }
+
+ public void setIncludeResource(String s) {
+ if (s != null)
+ set(INCLUDE_RESOURCE, s);
+ }
+
+ public void setBundleActivator(String s) {
+ if (s != null)
+ set(BUNDLE_ACTIVATOR, s);
+ }
+
+ public void setExportPackage(String s) {
+ if (s != null)
+ set(EXPORT_PACKAGE, s);
+ }
+
+ public void setImportPackage(String s) {
+ if (s != null)
+ set(IMPORT_PACKAGE, s);
+ }
+
+ public void setBundleClasspath(String s) {
+ if (s != null)
+ set(BUNDLE_CLASSPATH, s);
+ }
+
+ public Parameters getBundleClasspath() {
+ return getParameters(BUNDLE_CLASSPATH);
+ }
+
+ public void setBundleRequiredExecutionEnvironment(String s) {
+ if (s != null)
+ set(BUNDLE_REQUIREDEXECUTIONENVIRONMENT, s);
+ }
+
+ public Parameters getBundleRequiredExecutionEnvironment() {
+ return getParameters(BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
+ }
+
+ public void setSources(boolean b) {
+ if (b)
+ set(SOURCES, "true");
+ else
+ set(SOURCES, "false");
+ }
+
+ public boolean isSources() {
+ return Processor.isTrue(get(SOURCES));
+ }
+
+ public String getBundleSymbolicName() {
+ return get(BUNDLE_SYMBOLICNAME);
+ }
+
+ public void setBundleSymbolicName(String s) {
+ set(BUNDLE_SYMBOLICNAME, s);
+ }
+
+ public String getBundleVersion() {
+ return get(BUNDLE_VERSION);
+ }
+
+ public void setBundleVersion(String version) {
+ Version v = new Version(version);
+ set(BUNDLE_VERSION,v.toString());
+ }
+
+ public void setBundleVersion(Version version) {
+ set(BUNDLE_VERSION,version.toString());
+ }
+
+ public void setFailOk(boolean b) {
+ set(FAIL_OK, b+"");
+ }
+
+ public boolean isFailOk() {
+ return Processor.isTrue(get(FAIL_OK));
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/EmbeddedResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/EmbeddedResource.java
new file mode 100755
index 0000000..ebc93ee
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/EmbeddedResource.java
@@ -0,0 +1,98 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.zip.*;
+
+import aQute.lib.io.*;
+
+public class EmbeddedResource implements Resource {
+ byte data[];
+ long lastModified;
+ String extra;
+
+ public EmbeddedResource(byte data[], long lastModified) {
+ this.data = data;
+ this.lastModified = lastModified;
+ }
+
+ public InputStream openInputStream() throws FileNotFoundException {
+ return new ByteArrayInputStream(data);
+ }
+
+ public void write(OutputStream out) throws IOException {
+ out.write(data);
+ }
+
+ public String toString() {
+ return ":" + data.length + ":";
+ }
+
+ public static void build(Jar jar, InputStream in, long lastModified) throws IOException {
+ ZipInputStream jin = new ZipInputStream(in);
+ ZipEntry entry = jin.getNextEntry();
+ while (entry != null) {
+ if (!entry.isDirectory()) {
+ byte data[] = collect(jin);
+ jar.putResource(entry.getName(), new EmbeddedResource(data, lastModified), true);
+ }
+ entry = jin.getNextEntry();
+ }
+ IO.drain(in);
+ jin.close();
+ }
+
+ /**
+ * Convenience method to turn an inputstream into a byte array. The method
+ * uses a recursive algorithm to minimize memory usage.
+ *
+ * @param in
+ * stream with data
+ * @param offset
+ * where we are in the stream
+ * @returns byte array filled with data
+ */
+ static byte[] collect(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ copy(in, out);
+ return out.toByteArray();
+ }
+
+ static void copy(InputStream in, OutputStream out) throws IOException {
+ int available = in.available();
+ if (available <= 10000)
+ available = 64000;
+ byte[] buffer = new byte[available];
+ int size;
+ while ((size = in.read(buffer)) > 0)
+ out.write(buffer, 0, size);
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public static void build(Jar sub, Resource resource) throws Exception {
+ InputStream in = resource.openInputStream();
+ try {
+ build(sub, in, resource.lastModified());
+ } catch( Exception e ) {
+ e.printStackTrace();
+ }
+ finally {
+ in.close();
+ }
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public long size() {
+ return data.length;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java
new file mode 100755
index 0000000..b849878
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java
@@ -0,0 +1,85 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.regex.*;
+
+public class FileResource implements Resource {
+ File file;
+ String extra;
+
+ public FileResource(File file) {
+ this.file = file;
+ }
+
+ public InputStream openInputStream() throws FileNotFoundException {
+ return new FileInputStream(file);
+ }
+
+ public static void build(Jar jar, File directory, Pattern doNotCopy) {
+ traverse(
+ jar,
+ directory.getAbsolutePath().length(),
+ directory,
+ doNotCopy);
+ }
+
+ public String toString() {
+ return ":" + file.getName() + ":";
+ }
+
+ public void write(OutputStream out) throws Exception {
+ copy(this, out);
+ }
+
+ static synchronized void copy(Resource resource, OutputStream out)
+ throws Exception {
+ InputStream in = resource.openInputStream();
+ try {
+ byte buffer[] = new byte[20000];
+ int size = in.read(buffer);
+ while (size > 0) {
+ out.write(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ }
+ finally {
+ in.close();
+ }
+ }
+
+ static void traverse(Jar jar, int rootlength, File directory,
+ Pattern doNotCopy) {
+ if (doNotCopy != null && doNotCopy.matcher(directory.getName()).matches())
+ return;
+ jar.updateModified(directory.lastModified(), "Dir change");
+
+ File files[] = directory.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].isDirectory())
+ traverse(jar, rootlength, files[i], doNotCopy);
+ else {
+ String path = files[i].getAbsolutePath().substring(
+ rootlength + 1);
+ if (File.separatorChar != '/')
+ path = path.replace(File.separatorChar, '/');
+ jar.putResource(path, new FileResource(files[i]), true);
+ }
+ }
+ }
+
+ public long lastModified() {
+ return file.lastModified();
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public long size() {
+ return (int) file.length();
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java b/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
new file mode 100755
index 0000000..4b81607
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
@@ -0,0 +1,185 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.regex.*;
+
+public class Instruction {
+
+ public static class Filter implements FileFilter {
+
+ private Instruction instruction;
+ private boolean recursive;
+ private Pattern doNotCopy;
+
+ public Filter (Instruction instruction, boolean recursive, Pattern doNotCopy) {
+ this.instruction = instruction;
+ this.recursive = recursive;
+ this.doNotCopy = doNotCopy;
+ }
+ public Filter (Instruction instruction, boolean recursive) {
+ this(instruction, recursive, Pattern.compile(Constants.DEFAULT_DO_NOT_COPY));
+ }
+ public boolean isRecursive() {
+ return recursive;
+ }
+ public boolean accept(File pathname) {
+ if (doNotCopy != null && doNotCopy.matcher(pathname.getName()).matches()) {
+ return false;
+ }
+
+ if (pathname.isDirectory() && isRecursive()) {
+ return true;
+ }
+
+ if (instruction == null) {
+ return true;
+ }
+ return !instruction.isNegated() == instruction.matches(pathname.getName());
+ }
+ }
+
+ transient Pattern pattern;
+ transient boolean optional;
+
+ final String input;
+ final String match;
+ final boolean negated;
+ final boolean duplicate;
+ final boolean literal;
+ final boolean any;
+
+ public Instruction(String input) {
+ this.input = input;
+
+ String s = Processor.removeDuplicateMarker(input);
+ duplicate = !s.equals(input);
+
+ if (s.startsWith("!")) {
+ negated = true;
+ s = s.substring(1);
+ } else
+ negated = false;
+
+ if ( input.equals("*")) {
+ any = true;
+ literal= false;
+ match= null;
+ return;
+ }
+
+ any = false;
+ if (s.startsWith("=")) {
+ match = s.substring(1);
+ literal = true;
+ } else {
+ boolean wildcards = false;
+
+ StringBuilder sb = new StringBuilder();
+ loop: for (int c = 0; c < s.length(); c++) {
+ switch (s.charAt(c)) {
+ case '.':
+ // If we end in a wildcard .* then we need to
+ // also include the last full package. I.e.
+ // com.foo.* includes com.foo (unlike OSGi)
+ if ( c == s.length()-2 && '*'==s.charAt(c+1)) {
+ sb.append("(\\..*)?");
+ wildcards=true;
+ break loop;
+ }
+ else
+ sb.append("\\.");
+
+ break;
+ case '*':
+ sb.append(".*");
+ wildcards=true;
+ break;
+ case '$':
+ sb.append("\\$");
+ break;
+ case '?':
+ sb.append(".?");
+ wildcards=true;
+ break;
+ case '|':
+ sb.append('|');
+ wildcards=true;
+ break;
+ default:
+ sb.append(s.charAt(c));
+ break;
+ }
+ }
+
+ if ( !wildcards ) {
+ literal = true;
+ match = s;
+ } else {
+ literal = false;
+ match = sb.toString();
+ }
+ }
+
+
+ }
+
+ public boolean matches(String value) {
+ if (any)
+ return true;
+
+ if (literal )
+ return match.equals(value);
+ else
+ return getMatcher(value).matches();
+ }
+
+ public boolean isNegated() {
+ return negated;
+ }
+
+ public String getPattern() {
+ return match;
+ }
+
+ public String getInput() {
+ return input;
+ }
+
+ public String toString() {
+ return input;
+ }
+
+ public Matcher getMatcher(String value) {
+ if (pattern == null) {
+ pattern = Pattern.compile(match);
+ }
+ return pattern.matcher(value);
+ }
+
+ public void setOptional() {
+ optional = true;
+ }
+
+ public boolean isOptional() {
+ return optional;
+ }
+
+
+ public boolean isLiteral() {
+ return literal;
+ }
+
+ public String getLiteral() {
+ assert literal;
+ return match;
+ }
+
+ public boolean isDuplicate() {
+ return duplicate;
+ }
+
+ public boolean isAny() {
+ return any;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Instructions.java b/bundleplugin/src/main/java/aQute/lib/osgi/Instructions.java
new file mode 100644
index 0000000..e74150c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Instructions.java
@@ -0,0 +1,219 @@
+package aQute.lib.osgi;
+
+import java.util.*;
+
+import aQute.libg.header.*;
+
+public class Instructions implements Map<Instruction, Attrs> {
+ private LinkedHashMap<Instruction, Attrs> map;
+ static Map<Instruction, Attrs> EMPTY = Collections.emptyMap();
+
+ public Instructions(Instructions other) {
+ if (other.map != null && !other.map.isEmpty()) {
+ map = new LinkedHashMap<Instruction, Attrs>(other.map);
+ }
+ }
+
+ public Instructions(Collection<String> other) {
+ if ( other != null)
+ for ( String s : other ) {
+ put( new Instruction(s), null);
+ }
+ }
+
+ public Instructions() {
+ }
+
+ public Instructions(Parameters contained) {
+ append(contained);
+ }
+
+ public Instructions(String h) {
+ this(new Parameters(h));
+ }
+
+ public void clear() {
+ map.clear();
+ }
+
+ public boolean containsKey(Instruction name) {
+ if (map == null)
+ return false;
+
+ return map.containsKey(name);
+ }
+
+ @Deprecated public boolean containsKey(Object name) {
+ assert name instanceof Instruction;
+ if (map == null)
+ return false;
+
+ return map.containsKey((Instruction) name);
+ }
+
+ public boolean containsValue(Attrs value) {
+ if (map == null)
+ return false;
+
+ return map.containsValue(value);
+ }
+
+ @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<Instruction, Attrs>> entrySet() {
+ if (map == null)
+ return EMPTY.entrySet();
+
+ return map.entrySet();
+ }
+
+ @Deprecated public Attrs get(Object key) {
+ assert key instanceof Instruction;
+ if (map == null)
+ return null;
+
+ return map.get((Instruction) key);
+ }
+
+ public Attrs get(Instruction key) {
+ if (map == null)
+ return null;
+
+ return map.get(key);
+ }
+
+ public boolean isEmpty() {
+ return map == null || map.isEmpty();
+ }
+
+ public Set<Instruction> keySet() {
+ if (map == null)
+ return EMPTY.keySet();
+
+ return map.keySet();
+ }
+
+ public Attrs put(Instruction key, Attrs value) {
+ if (map == null)
+ map = new LinkedHashMap<Instruction, Attrs>();
+
+ return map.put(key, value);
+ }
+
+ public void putAll(Map<? extends Instruction, ? extends Attrs> map) {
+ if (this.map == null)
+ if (map.isEmpty())
+ return;
+ else
+ this.map = new LinkedHashMap<Instruction, Attrs>();
+ this.map.putAll(map);
+ }
+
+ @Deprecated public Attrs remove(Object var0) {
+ assert var0 instanceof Instruction;
+ if (map == null)
+ return null;
+
+ return map.remove((Instruction) var0);
+ }
+
+ public Attrs remove(Instruction 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() {
+ return map == null ? "{}" : map.toString();
+ }
+
+ public void append(Parameters other) {
+ for (Map.Entry<String, Attrs> e : other.entrySet()) {
+ put( new Instruction(e.getKey()), e.getValue());
+ }
+ }
+ public <T> Collection<T> select(Collection<T> set, boolean emptyIsAll) {
+ return select(set,null, emptyIsAll);
+ }
+
+ public <T> Collection<T> select(Collection<T> set, Set<Instruction> unused, boolean emptyIsAll) {
+ List<T> input = new ArrayList<T>(set);
+ if ( emptyIsAll && isEmpty())
+ return input;
+
+ List<T> result = new ArrayList<T>();
+
+ for (Instruction instruction : keySet()) {
+ boolean used = false;
+ for (Iterator<T> o = input.iterator(); o.hasNext();) {
+ T oo = o.next();
+ String s = oo.toString();
+ if (instruction.matches(s)) {
+ if (!instruction.isNegated())
+ result.add(oo);
+ o.remove();
+ used = true;
+ }
+ }
+ if ( !used && unused != null)
+ unused.add(instruction);
+ }
+ return result;
+ }
+
+
+ public <T> Collection<T> reject(Collection<T> set) {
+ List<T> input = new ArrayList<T>(set);
+ List<T> result = new ArrayList<T>();
+
+ for (Instruction instruction : keySet()) {
+ for (Iterator<T> o = input.iterator(); o.hasNext();) {
+ T oo = o.next();
+ String s = oo.toString();
+ if (instruction.matches(s)) {
+ if (instruction.isNegated())
+ result.add(oo);
+ o.remove();
+ } else
+ result.add(oo);
+
+ }
+ }
+ return result;
+ }
+
+ public boolean matches(String value) {
+ if ( size() == 0)
+ return true;
+
+ for ( Instruction i : keySet()) {
+ if ( i.matches(value)) {
+ if ( i.isNegated())
+ return false; // we deny this one explicitly
+ else
+ return true; // we allow it explicitly
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java b/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java
new file mode 100755
index 0000000..497198b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java
@@ -0,0 +1,783 @@
+package aQute.lib.osgi;
+
+import static aQute.lib.io.IO.*;
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.lib.base64.*;
+import aQute.lib.io.*;
+import aQute.libg.reporter.*;
+
+public class Jar implements Closeable {
+ public enum Compression {
+ DEFLATE, STORE
+ }
+
+ public static final Object[] EMPTY_ARRAY = new Jar[0];
+ final Map<String, Resource> resources = new TreeMap<String, Resource>();
+ final Map<String, Map<String, Resource>> directories = new TreeMap<String, Map<String, Resource>>();
+ Manifest manifest;
+ boolean manifestFirst;
+ String name;
+ File source;
+ ZipFile zipFile;
+ long lastModified;
+ String lastModifiedReason;
+ Reporter reporter;
+ boolean doNotTouchManifest;
+ boolean nomanifest;
+ Compression compression = Compression.DEFLATE;
+ boolean closed;
+
+ public Jar(String name) {
+ this.name = name;
+ }
+
+ public Jar(String name, File dirOrFile, Pattern doNotCopy) throws ZipException, IOException {
+ this(name);
+ source = dirOrFile;
+ if (dirOrFile.isDirectory())
+ FileResource.build(this, dirOrFile, doNotCopy);
+ else if (dirOrFile.isFile()) {
+ zipFile = ZipResource.build(this, dirOrFile);
+ } else {
+ throw new IllegalArgumentException("A Jar can only accept a valid file or directory: "
+ + dirOrFile);
+ }
+ }
+
+ public Jar(String name, InputStream in, long lastModified) throws IOException {
+ this(name);
+ EmbeddedResource.build(this, in, lastModified);
+ }
+
+ public Jar(String name, String path) throws IOException {
+ this(name);
+ File f = new File(path);
+ InputStream in = new FileInputStream(f);
+ EmbeddedResource.build(this, in, f.lastModified());
+ in.close();
+ }
+
+ public Jar(File f) throws IOException {
+ this(getName(f), f, null);
+ }
+
+ /**
+ * Make the JAR file name the project name if we get a src or bin directory.
+ *
+ * @param f
+ * @return
+ */
+ private static String getName(File f) {
+ f = f.getAbsoluteFile();
+ String name = f.getName();
+ if (name.equals("bin") || name.equals("src"))
+ return f.getParentFile().getName();
+ else {
+ if (name.endsWith(".jar"))
+ name = name.substring(0, name.length() - 4);
+ return name;
+ }
+ }
+
+ public Jar(String string, InputStream resourceAsStream) throws IOException {
+ this(string, resourceAsStream, 0);
+ }
+
+ public Jar(String string, File file) throws ZipException, IOException {
+ this(string, file, Pattern.compile(Constants.DEFAULT_DO_NOT_COPY));
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return "Jar:" + name;
+ }
+
+ public boolean putResource(String path, Resource resource) {
+ check();
+ return putResource(path, resource, true);
+ }
+
+ public boolean putResource(String path, Resource resource, boolean overwrite) {
+ check();
+ updateModified(resource.lastModified(), path);
+ while (path.startsWith("/"))
+ path = path.substring(1);
+
+ if (path.equals("META-INF/MANIFEST.MF")) {
+ manifest = null;
+ if (resources.isEmpty())
+ manifestFirst = true;
+ }
+ String dir = getDirectory(path);
+ Map<String, Resource> s = directories.get(dir);
+ if (s == null) {
+ s = new TreeMap<String, Resource>();
+ directories.put(dir, s);
+ int n = dir.lastIndexOf('/');
+ while (n > 0) {
+ String dd = dir.substring(0, n);
+ if (directories.containsKey(dd))
+ break;
+ directories.put(dd, null);
+ n = dd.lastIndexOf('/');
+ }
+ }
+ boolean duplicate = s.containsKey(path);
+ if (!duplicate || overwrite) {
+ resources.put(path, resource);
+ s.put(path, resource);
+ }
+ return duplicate;
+ }
+
+ public Resource getResource(String path) {
+ check();
+ if (resources == null)
+ return null;
+ return resources.get(path);
+ }
+
+ private String getDirectory(String path) {
+ check();
+ int n = path.lastIndexOf('/');
+ if (n < 0)
+ return "";
+
+ return path.substring(0, n);
+ }
+
+ public Map<String, Map<String, Resource>> getDirectories() {
+ check();
+ return directories;
+ }
+
+ public Map<String, Resource> getResources() {
+ check();
+ return resources;
+ }
+
+ public boolean addDirectory(Map<String, Resource> directory, boolean overwrite) {
+ check();
+ boolean duplicates = false;
+ if (directory == null)
+ return false;
+
+ for (Map.Entry<String, Resource> entry : directory.entrySet()) {
+ String key = entry.getKey();
+ if (!key.endsWith(".java")) {
+ duplicates |= putResource(key, entry.getValue(), overwrite);
+ }
+ }
+ return duplicates;
+ }
+
+ public Manifest getManifest() throws Exception {
+ check();
+ if (manifest == null) {
+ Resource manifestResource = getResource("META-INF/MANIFEST.MF");
+ if (manifestResource != null) {
+ InputStream in = manifestResource.openInputStream();
+ manifest = new Manifest(in);
+ in.close();
+ }
+ }
+ return manifest;
+ }
+
+ public boolean exists(String path) {
+ check();
+ return resources.containsKey(path);
+ }
+
+ public void setManifest(Manifest manifest) {
+ check();
+ manifestFirst = true;
+ this.manifest = manifest;
+ }
+
+ public void setManifest(File file) throws IOException {
+ check();
+ FileInputStream fin = new FileInputStream(file);
+ try {
+ Manifest m = new Manifest(fin);
+ setManifest(m);
+ } finally {
+ fin.close();
+ }
+ }
+
+ public void write(File file) throws Exception {
+ check();
+ try {
+ OutputStream out = new FileOutputStream(file);
+ try {
+ write(out);
+ } finally {
+ IO.close(out);
+ }
+ return;
+
+ } catch (Exception t) {
+ file.delete();
+ throw t;
+ }
+ }
+
+ public void write(String file) throws Exception {
+ check();
+ write(new File(file));
+ }
+
+ public void write(OutputStream out) throws Exception {
+ check();
+ ZipOutputStream jout = nomanifest || doNotTouchManifest ? new ZipOutputStream(out)
+ : new JarOutputStream(out);
+
+ switch (compression) {
+ case STORE:
+ jout.setMethod(ZipOutputStream.DEFLATED);
+ break;
+
+ default:
+ // default is DEFLATED
+ }
+
+ Set<String> done = new HashSet<String>();
+
+ Set<String> directories = new HashSet<String>();
+ if (doNotTouchManifest) {
+ Resource r = getResource("META-INF/MANIFEST.MF");
+ if (r != null) {
+ writeResource(jout, directories, "META-INF/MANIFEST.MF", r);
+ done.add("META-INF/MANIFEST.MF");
+ }
+ } else
+ doManifest(done, jout);
+
+ for (Map.Entry<String, Resource> entry : getResources().entrySet()) {
+ // Skip metainf contents
+ if (!done.contains(entry.getKey()))
+ writeResource(jout, directories, entry.getKey(), entry.getValue());
+ }
+ jout.finish();
+ }
+
+ private void doManifest(Set<String> done, ZipOutputStream jout) throws Exception {
+ check();
+ if (nomanifest)
+ return;
+
+ JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
+ jout.putNextEntry(ze);
+ writeManifest(jout);
+ jout.closeEntry();
+ done.add(ze.getName());
+ }
+
+ /**
+ * Cleanup the manifest for writing. Cleaning up consists of adding a space
+ * after any \n to prevent the manifest to see this newline as a delimiter.
+ *
+ * @param out
+ * Output
+ * @throws IOException
+ */
+
+ public void writeManifest(OutputStream out) throws Exception {
+ check();
+ writeManifest(getManifest(), out);
+ }
+
+ public static void writeManifest(Manifest manifest, OutputStream out) throws IOException {
+ if (manifest == null)
+ return;
+
+ manifest = clean(manifest);
+ outputManifest(manifest, out);
+ }
+
+ /**
+ * Unfortunately we have to write our own manifest :-( because of a stupid
+ * bug in the manifest code. It tries to handle UTF-8 but the way it does it
+ * it makes the bytes platform dependent.
+ *
+ * So the following code outputs the manifest.
+ *
+ * A Manifest consists of
+ *
+ * <pre>
+ * 'Manifest-Version: 1.0\r\n'
+ * main-attributes *
+ * \r\n
+ * name-section
+ *
+ * main-attributes ::= attributes
+ * attributes ::= key ': ' value '\r\n'
+ * name-section ::= 'Name: ' name '\r\n' attributes
+ * </pre>
+ *
+ * Lines in the manifest should not exceed 72 bytes (! this is where the
+ * manifest screwed up as well when 16 bit unicodes were used).
+ *
+ * <p>
+ * As a bonus, we can now sort the manifest!
+ */
+ static byte[] CONTINUE = new byte[] { '\r', '\n', ' ' };
+
+ /**
+ * Main function to output a manifest properly in UTF-8.
+ *
+ * @param manifest
+ * The manifest to output
+ * @param out
+ * The output stream
+ * @throws IOException
+ * when something fails
+ */
+ public static void outputManifest(Manifest manifest, OutputStream out) throws IOException {
+ writeEntry(out, "Manifest-Version", "1.0");
+ attributes(manifest.getMainAttributes(), out);
+
+ TreeSet<String> keys = new TreeSet<String>();
+ for (Object o : manifest.getEntries().keySet())
+ keys.add(o.toString());
+
+ for (String key : keys) {
+ write(out, 0, "\r\n");
+ writeEntry(out, "Name", key);
+ attributes(manifest.getAttributes(key), out);
+ }
+ out.flush();
+ }
+
+ /**
+ * Write out an entry, handling proper unicode and line length constraints
+ *
+ */
+ private static void writeEntry(OutputStream out, String name, String value) throws IOException {
+ int n = write(out, 0, name + ": ");
+ write(out, n, value);
+ write(out, 0, "\r\n");
+ }
+
+ /**
+ * Convert a string to bytes with UTF8 and then output in max 72 bytes
+ *
+ * @param out
+ * the output string
+ * @param i
+ * the current width
+ * @param s
+ * the string to output
+ * @return the new width
+ * @throws IOException
+ * when something fails
+ */
+ private static int write(OutputStream out, int i, String s) throws IOException {
+ byte[] bytes = s.getBytes("UTF8");
+ return write(out, i, bytes);
+ }
+
+ /**
+ * Write the bytes but ensure that the line length does not exceed 72
+ * characters. If it is more than 70 characters, we just put a cr/lf +
+ * space.
+ *
+ * @param out
+ * The output stream
+ * @param width
+ * The nr of characters output in a line before this method
+ * started
+ * @param bytes
+ * the bytes to output
+ * @return the nr of characters in the last line
+ * @throws IOException
+ * if something fails
+ */
+ private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
+ int w = width;
+ for (int i = 0; i < bytes.length; i++) {
+ if (w >= 72) { // we need to add the \n\r!
+ out.write(CONTINUE);
+ w = 1;
+ }
+ out.write(bytes[i]);
+ w++;
+ }
+ return w;
+ }
+
+ /**
+ * Output an Attributes map. We will sort this map before outputing.
+ *
+ * @param value
+ * the attrbutes
+ * @param out
+ * the output stream
+ * @throws IOException
+ * when something fails
+ */
+ private static void attributes(Attributes value, OutputStream out) throws IOException {
+ TreeMap<String, String> map = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+ for (Map.Entry<Object, Object> entry : value.entrySet()) {
+ map.put(entry.getKey().toString(), entry.getValue().toString());
+ }
+
+ map.remove("Manifest-Version"); // get rid of
+ // manifest
+ // version
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ writeEntry(out, entry.getKey(), entry.getValue());
+ }
+ }
+
+ private static Manifest clean(Manifest org) {
+
+ Manifest result = new Manifest();
+ for (Map.Entry<?, ?> entry : org.getMainAttributes().entrySet()) {
+ String nice = clean((String) entry.getValue());
+ result.getMainAttributes().put(entry.getKey(), nice);
+ }
+ for (String name : org.getEntries().keySet()) {
+ Attributes attrs = result.getAttributes(name);
+ if (attrs == null) {
+ attrs = new Attributes();
+ result.getEntries().put(name, attrs);
+ }
+
+ for (Map.Entry<?, ?> entry : org.getAttributes(name).entrySet()) {
+ String nice = clean((String) entry.getValue());
+ attrs.put((Attributes.Name) entry.getKey(), nice);
+ }
+ }
+ return result;
+ }
+
+ private static String clean(String s) {
+ if (s.indexOf('\n') < 0)
+ return s;
+
+ StringBuilder sb = new StringBuilder(s);
+ for (int i = 0; i < sb.length(); i++) {
+ if (sb.charAt(i) == '\n')
+ sb.insert(++i, ' ');
+ }
+ return sb.toString();
+ }
+
+ private void writeResource(ZipOutputStream jout, Set<String> directories, String path,
+ Resource resource) throws Exception {
+ if (resource == null)
+ return;
+ try {
+ createDirectories(directories, jout, path);
+ ZipEntry ze = new ZipEntry(path);
+ ze.setMethod(ZipEntry.DEFLATED);
+ long lastModified = resource.lastModified();
+ if (lastModified == 0L) {
+ lastModified = System.currentTimeMillis();
+ }
+ ze.setTime(lastModified);
+ if (resource.getExtra() != null)
+ ze.setExtra(resource.getExtra().getBytes("UTF-8"));
+ jout.putNextEntry(ze);
+ resource.write(jout);
+ jout.closeEntry();
+ } catch (Exception e) {
+ throw new Exception("Problem writing resource " + path, e);
+ }
+ }
+
+ void createDirectories(Set<String> directories, ZipOutputStream zip, String name)
+ throws IOException {
+ int index = name.lastIndexOf('/');
+ if (index > 0) {
+ String path = name.substring(0, index);
+ if (directories.contains(path))
+ return;
+ createDirectories(directories, zip, path);
+ ZipEntry ze = new ZipEntry(path + '/');
+ zip.putNextEntry(ze);
+ zip.closeEntry();
+ directories.add(path);
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Add all the resources in the given jar that match the given filter.
+ *
+ * @param sub
+ * the jar
+ * @param filter
+ * a pattern that should match the resoures in sub to be added
+ */
+ public boolean addAll(Jar sub, Instruction filter) {
+ return addAll(sub, filter, "");
+ }
+
+ /**
+ * Add all the resources in the given jar that match the given filter.
+ *
+ * @param sub
+ * the jar
+ * @param filter
+ * a pattern that should match the resoures in sub to be added
+ */
+ public boolean addAll(Jar sub, Instruction filter, String destination) {
+ check();
+ boolean dupl = false;
+ for (String name : sub.getResources().keySet()) {
+ if ("META-INF/MANIFEST.MF".equals(name))
+ continue;
+
+ if (filter == null || filter.matches(name) != filter.isNegated())
+ dupl |= putResource(Processor.appendPath(destination, name), sub.getResource(name),
+ true);
+ }
+ return dupl;
+ }
+
+ public void close() {
+ this.closed = true;
+ if (zipFile != null)
+ try {
+ zipFile.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ resources.clear();
+ directories.clear();
+ manifest = null;
+ source = null;
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public void updateModified(long time, String reason) {
+ if (time > lastModified) {
+ lastModified = time;
+ lastModifiedReason = reason;
+ }
+ }
+
+ public void setReporter(Reporter reporter) {
+ this.reporter = reporter;
+ }
+
+ public boolean hasDirectory(String path) {
+ check();
+ return directories.get(path) != null;
+ }
+
+ public List<String> getPackages() {
+ check();
+ List<String> list = new ArrayList<String>(directories.size());
+
+ for (Map.Entry<String, Map<String, Resource>> i : directories.entrySet()) {
+ if (i.getValue() != null) {
+ String path = i.getKey();
+ String pack = path.replace('/', '.');
+ list.add(pack);
+ }
+ }
+ return list;
+ }
+
+ public File getSource() {
+ check();
+ return source;
+ }
+
+ public boolean addAll(Jar src) {
+ check();
+ return addAll(src, null);
+ }
+
+ public boolean rename(String oldPath, String newPath) {
+ check();
+ Resource resource = remove(oldPath);
+ if (resource == null)
+ return false;
+
+ return putResource(newPath, resource);
+ }
+
+ public Resource remove(String path) {
+ check();
+ Resource resource = resources.remove(path);
+ String dir = getDirectory(path);
+ Map<String, Resource> mdir = directories.get(dir);
+ // must be != null
+ mdir.remove(path);
+ return resource;
+ }
+
+ /**
+ * Make sure nobody touches the manifest! If the bundle is signed, we do not
+ * want anybody to touch the manifest after the digests have been
+ * calculated.
+ */
+ public void setDoNotTouchManifest() {
+ doNotTouchManifest = true;
+ }
+
+ /**
+ * Calculate the checksums and set them in the manifest.
+ */
+
+ public void calcChecksums(String algorithms[]) throws Exception {
+ check();
+ if (algorithms == null)
+ algorithms = new String[] { "SHA", "MD5" };
+
+ Manifest m = getManifest();
+ if (m == null) {
+ m = new Manifest();
+ setManifest(m);
+ }
+
+ MessageDigest digests[] = new MessageDigest[algorithms.length];
+ int n = 0;
+ for (String algorithm : algorithms)
+ digests[n++] = MessageDigest.getInstance(algorithm);
+
+ byte buffer[] = new byte[30000];
+
+ for (Map.Entry<String, Resource> entry : resources.entrySet()) {
+
+ // Skip the manifest
+ if (entry.getKey().equals("META-INF/MANIFEST.MF"))
+ continue;
+
+ Resource r = entry.getValue();
+ Attributes attributes = m.getAttributes(entry.getKey());
+ if (attributes == null) {
+ attributes = new Attributes();
+ getManifest().getEntries().put(entry.getKey(), attributes);
+ }
+ InputStream in = r.openInputStream();
+ try {
+ for (MessageDigest d : digests)
+ d.reset();
+ int size = in.read(buffer);
+ while (size > 0) {
+ for (MessageDigest d : digests)
+ d.update(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ } finally {
+ in.close();
+ }
+ for (MessageDigest d : digests)
+ attributes.putValue(d.getAlgorithm() + "-Digest", Base64.encodeBase64(d.digest()));
+ }
+ }
+
+ Pattern BSN = Pattern.compile("\\s*([-\\w\\d\\._]+)\\s*;?.*");
+
+ public String getBsn() throws Exception {
+ check();
+ Manifest m = getManifest();
+ if (m == null)
+ return null;
+
+ String s = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+ if (s == null)
+ return null;
+
+ Matcher matcher = BSN.matcher(s);
+ if (matcher.matches()) {
+ return matcher.group(1);
+ }
+ return null;
+ }
+
+ public String getVersion() throws Exception {
+ check();
+ Manifest m = getManifest();
+ if (m == null)
+ return null;
+
+ String s = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+ if (s == null)
+ return null;
+
+ return s.trim();
+ }
+
+ /**
+ * Expand the JAR file to a directory.
+ *
+ * @param dir
+ * the dst directory, is not required to exist
+ * @throws Exception
+ * if anything does not work as expected.
+ */
+ public void expand(File dir) throws Exception {
+ check();
+ dir = dir.getAbsoluteFile();
+ dir.mkdirs();
+ if (!dir.isDirectory()) {
+ throw new IllegalArgumentException("Not a dir: " + dir.getAbsolutePath());
+ }
+
+ for (Map.Entry<String, Resource> entry : getResources().entrySet()) {
+ File f = getFile(dir, entry.getKey());
+ f.getParentFile().mkdirs();
+ IO.copy(entry.getValue().openInputStream(), f);
+ }
+ }
+
+ /**
+ * Make sure we have a manifest
+ *
+ * @throws Exception
+ */
+ public void ensureManifest() throws Exception {
+ if (getManifest() != null)
+ return;
+ manifest = new Manifest();
+ }
+
+ /**
+ * Answer if the manifest was the first entry
+ */
+
+ public boolean isManifestFirst() {
+ return manifestFirst;
+ }
+
+ public void copy(Jar srce, String path, boolean overwrite) {
+ check();
+ addDirectory(srce.getDirectories().get(path), overwrite);
+ }
+
+ public void setCompression(Compression compression) {
+ this.compression = compression;
+ }
+
+ public Compression hasCompression() {
+ return this.compression;
+ }
+
+ void check() {
+ if (closed)
+ throw new RuntimeException("Already closed " + name);
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java
new file mode 100755
index 0000000..0c0adcd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java
@@ -0,0 +1,34 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public class JarResource extends WriteResource {
+ Jar jar;
+ long size = -1;
+
+ public JarResource(Jar jar) {
+ this.jar = jar;
+ }
+
+ public long lastModified() {
+ return jar.lastModified();
+ }
+
+ public void write(OutputStream out) throws Exception {
+ try {
+ jar.write(out);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ public Jar getJar() {
+ return jar;
+ }
+
+ public String toString() {
+ return ":" + jar.getName() + ":";
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java b/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java
new file mode 100755
index 0000000..4ddd625
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java
@@ -0,0 +1,1015 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.text.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.lib.collections.*;
+import aQute.lib.io.*;
+import aQute.libg.sed.*;
+import aQute.libg.version.*;
+
+/**
+ * Provide a macro processor. This processor can replace variables in strings
+ * based on a properties and a domain. The domain can implement functions that
+ * start with a "_" and take args[], the names of these functions are available
+ * as functions in the macro processor (without the _). Macros can nest to any
+ * depth but may not contain loops.
+ *
+ * Add POSIX macros: ${#parameter} String length.
+ *
+ * ${parameter%word} Remove smallest suffix pattern.
+ *
+ * ${parameter%%word} Remove largest suffix pattern.
+ *
+ * ${parameter#word} Remove smallest prefix pattern.
+ *
+ * ${parameter##word} Remove largest prefix pattern.
+ */
+public class Macro implements Replacer {
+ Processor domain;
+ Object targets[];
+ boolean flattening;
+
+ public Macro(Processor domain, Object... targets) {
+ this.domain = domain;
+ this.targets = targets;
+ if (targets != null) {
+ for (Object o : targets) {
+ assert o != null;
+ }
+ }
+ }
+
+ public String process(String line, Processor source) {
+ return process(line, new Link(source, null, line));
+ }
+
+ String process(String line, Link link) {
+ StringBuilder sb = new StringBuilder();
+ process(line, 0, '\u0000', '\u0000', sb, link);
+ return sb.toString();
+ }
+
+ int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link) {
+ StringBuilder line = new StringBuilder(org);
+ int nesting = 1;
+
+ StringBuilder variable = new StringBuilder();
+ outer: while (index < line.length()) {
+ char c1 = line.charAt(index++);
+ if (c1 == end) {
+ if (--nesting == 0) {
+ result.append(replace(variable.toString(), link));
+ return index;
+ }
+ }
+ else
+ if (c1 == begin)
+ nesting++;
+ else
+ if (c1 == '\\' && index < line.length() - 1 && line.charAt(index) == '$') {
+ // remove the escape backslash and interpret the dollar
+ // as a
+ // literal
+ index++;
+ variable.append('$');
+ continue outer;
+ }
+ else
+ if (c1 == '$' && index < line.length() - 2) {
+ char c2 = line.charAt(index);
+ char terminator = getTerminator(c2);
+ if (terminator != 0) {
+ index = process(line, index + 1, c2, terminator, variable, link);
+ continue outer;
+ }
+ }
+ else
+ if (c1 == '.' && index < line.length() && line.charAt(index) == '/') {
+ // Found the sequence ./
+ if (index == 1 || Character.isWhitespace(line.charAt(index - 2))) {
+ // make sure it is preceded by whitespace or starts at begin
+ index++;
+ variable.append(domain.getBase().getAbsolutePath());
+ variable.append('/');
+ continue outer;
+ }
+ }
+ variable.append(c1);
+ }
+ result.append(variable);
+ return index;
+ }
+
+ public static char getTerminator(char c) {
+ switch (c) {
+ case '(' :
+ return ')';
+ case '[' :
+ return ']';
+ case '{' :
+ return '}';
+ case '<' :
+ return '>';
+ case '\u00ab' : // Guillemet double << >>
+ return '\u00bb';
+ case '\u2039' : // Guillemet single
+ return '\u203a';
+ }
+ return 0;
+ }
+
+ protected String replace(String key, Link link) {
+ if (link != null && link.contains(key))
+ return "${infinite:" + link.toString() + "}";
+
+ if (key != null) {
+ key = key.trim();
+ if (key.length() > 0) {
+ Processor source = domain;
+ String value = null;
+
+ if (key.indexOf(';') < 0) {
+ Instruction ins = new Instruction(key);
+ if (!ins.isLiteral()) {
+ SortedList<String> sortedList = SortedList.fromIterator(domain.iterator());
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (String k : sortedList) {
+ if (ins.matches(k)) {
+ String v = replace(k, new Link(source, link, key));
+ if (v != null) {
+ sb.append(del);
+ del = ",";
+ sb.append(v);
+ }
+ }
+ }
+ return sb.toString();
+ }
+ }
+ while (value == null && source != null) {
+ value = source.getProperties().getProperty(key);
+ source = source.getParent();
+ }
+
+ if (value != null)
+ return process(value, new Link(source, link, key));
+
+ value = doCommands(key, link);
+ if (value != null)
+ return process(value, new Link(source, link, key));
+
+ if (key != null && key.trim().length() > 0) {
+ value = System.getProperty(key);
+ if (value != null)
+ return value;
+ }
+ if (!flattening && !key.equals("@"))
+ domain.warning("No translation found for macro: " + key);
+ }
+ else {
+ domain.warning("Found empty macro key");
+ }
+ }
+ else {
+ domain.warning("Found null macro key");
+ }
+ return "${" + key + "}";
+ }
+
+ /**
+ * Parse the key as a command. A command consist of parameters separated by
+ * ':'.
+ *
+ * @param key
+ * @return
+ */
+ static Pattern commands = Pattern.compile("(?<!\\\\);");
+
+ private String doCommands(String key, Link source) {
+ String[] args = commands.split(key);
+ if (args == null || args.length == 0)
+ return null;
+
+ for (int i = 0; i < args.length; i++)
+ if (args[i].indexOf('\\') >= 0)
+ args[i] = args[i].replaceAll("\\\\;", ";");
+
+ if (args[0].startsWith("^")) {
+ String varname = args[0].substring(1).trim();
+
+ Processor parent = source.start.getParent();
+ if (parent != null)
+ return parent.getProperty(varname);
+ else
+ return null;
+ }
+
+ Processor rover = domain;
+ while (rover != null) {
+ String result = doCommand(rover, args[0], args);
+ if (result != null)
+ return result;
+
+ rover = rover.getParent();
+ }
+
+ for (int i = 0; targets != null && i < targets.length; i++) {
+ String result = doCommand(targets[i], args[0], args);
+ if (result != null)
+ return result;
+ }
+
+ return doCommand(this, args[0], args);
+ }
+
+ private String doCommand(Object target, String method, String[] args) {
+ if (target == null)
+ ; // System.err.println("Huh? Target should never be null " +
+ // domain);
+ else {
+ String cname = "_" + method.replaceAll("-", "_");
+ try {
+ Method m = target.getClass().getMethod(cname, new Class[] {String[].class});
+ return (String) m.invoke(target, new Object[] {args});
+ }
+ catch (NoSuchMethodException e) {
+ // Ignore
+ }
+ catch (InvocationTargetException e) {
+ if (e.getCause() instanceof IllegalArgumentException) {
+ domain.error("%s, for cmd: %s, arguments; %s", e.getMessage(), method,
+ Arrays.toString(args));
+ }
+ else {
+ domain.warning("Exception in replace: " + e.getCause());
+ e.getCause().printStackTrace();
+ }
+ }
+ catch (Exception e) {
+ domain.warning("Exception in replace: " + e + " method=" + method);
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return a unique list where the duplicates are removed.
+ *
+ * @param args
+ * @return
+ */
+ static String _uniqHelp = "${uniq;<list> ...}";
+
+ public String _uniq(String args[]) {
+ verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
+ Set<String> set = new LinkedHashSet<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], set);
+ }
+ return Processor.join(set, ",");
+ }
+
+ public String _pathseparator(String args[]) {
+ return File.pathSeparator;
+ }
+
+ public String _separator(String args[]) {
+ return File.separator;
+ }
+
+ public String _filter(String args[]) {
+ return filter(args, false);
+ }
+
+ public String _filterout(String args[]) {
+ return filter(args, true);
+
+ }
+
+ static String _filterHelp = "${%s;<list>;<regex>}";
+
+ String filter(String[] args, boolean include) {
+ verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
+
+ Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
+ Pattern pattern = Pattern.compile(args[2]);
+
+ for (Iterator<String> i = list.iterator(); i.hasNext();) {
+ if (pattern.matcher(i.next()).matches() == include)
+ i.remove();
+ }
+ return Processor.join(list);
+ }
+
+ static String _sortHelp = "${sort;<list>...}";
+
+ public String _sort(String args[]) {
+ verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
+
+ List<String> result = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], result);
+ }
+ Collections.sort(result);
+ return Processor.join(result);
+ }
+
+ static String _joinHelp = "${join;<list>...}";
+
+ public String _join(String args[]) {
+
+ verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
+
+ List<String> result = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], result);
+ }
+ return Processor.join(result);
+ }
+
+ static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
+
+ public String _if(String args[]) {
+ verifyCommand(args, _ifHelp, null, 3, 4);
+ String condition = args[1].trim();
+ if (!condition.equalsIgnoreCase("false"))
+ if (condition.length() != 0)
+ return args[2];
+
+ if (args.length > 3)
+ return args[3];
+ else
+ return "";
+ }
+
+ public String _now(String args[]) {
+ return new Date().toString();
+ }
+
+ public final static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
+
+ public String _fmodified(String args[]) throws Exception {
+ verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
+
+ long time = 0;
+ Collection<String> names = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], names);
+ }
+ for (String name : names) {
+ File f = new File(name);
+ if (f.exists() && f.lastModified() > time)
+ time = f.lastModified();
+ }
+ return "" + time;
+ }
+
+ public String _long2date(String args[]) {
+ try {
+ return new Date(Long.parseLong(args[1])).toString();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "not a valid long";
+ }
+
+ public String _literal(String args[]) {
+ if (args.length != 2)
+ throw new RuntimeException("Need a value for the ${literal;<value>} macro");
+ return "${" + args[1] + "}";
+ }
+
+ public String _def(String args[]) {
+ if (args.length != 2)
+ throw new RuntimeException("Need a value for the ${def;<value>} macro");
+
+ return domain.getProperty(args[1], "");
+ }
+
+ /**
+ *
+ * replace ; <list> ; regex ; replace
+ *
+ * @param args
+ * @return
+ */
+ public String _replace(String args[]) {
+ if (args.length != 4) {
+ domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
+ return null;
+ }
+
+ String list[] = args[1].split("\\s*,\\s*");
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (int i = 0; i < list.length; i++) {
+ String element = list[i].trim();
+ if (!element.equals("")) {
+ sb.append(del);
+ sb.append(element.replaceAll(args[2], args[3]));
+ del = ", ";
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public String _warning(String args[]) {
+ for (int i = 1; i < args.length; i++) {
+ domain.warning(process(args[i]));
+ }
+ return "";
+ }
+
+ public String _error(String args[]) {
+ for (int i = 1; i < args.length; i++) {
+ domain.error(process(args[i]));
+ }
+ return "";
+ }
+
+ /**
+ * toclassname ; <path>.class ( , <path>.class ) *
+ *
+ * @param args
+ * @return
+ */
+ static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
+
+ public String _toclassname(String args[]) {
+ verifyCommand(args, _toclassnameHelp, null, 2, 2);
+ Collection<String> paths = Processor.split(args[1]);
+
+ List<String> names = new ArrayList<String>(paths.size());
+ for (String path : paths) {
+ if (path.endsWith(".class")) {
+ String name = path.substring(0, path.length() - 6).replace('/', '.');
+ names.add(name);
+ }
+ else
+ if (path.endsWith(".java")) {
+ String name = path.substring(0, path.length() - 5).replace('/', '.');
+ names.add(name);
+ }
+ else {
+ domain.warning("in toclassname, " + args[1]
+ + " is not a class path because it does not end in .class");
+ }
+ }
+ return Processor.join(names, ",");
+ }
+
+ /**
+ * toclassname ; <path>.class ( , <path>.class ) *
+ *
+ * @param args
+ * @return
+ */
+
+ static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
+
+ public String _toclasspath(String args[]) {
+ verifyCommand(args, _toclasspathHelp, null, 2, 3);
+ boolean cl = true;
+ if (args.length > 2)
+ cl = Boolean.valueOf(args[2]);
+
+ Collection<String> names = Processor.split(args[1]);
+ Collection<String> paths = new ArrayList<String>(names.size());
+ for (String name : names) {
+ String path = name.replace('.', '/') + (cl ? ".class" : "");
+ paths.add(path);
+ }
+ return Processor.join(paths, ",");
+ }
+
+ public String _dir(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${dir;...}");
+ return null;
+ }
+ else {
+ String del = "";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i < args.length; i++) {
+ File f = domain.getFile(args[i]);
+ if (f.exists() && f.getParentFile().exists()) {
+ sb.append(del);
+ sb.append(f.getParentFile().getAbsolutePath());
+ del = ",";
+ }
+ }
+ return sb.toString();
+ }
+
+ }
+
+ public String _basename(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${basename;...}");
+ return null;
+ }
+ else {
+ String del = "";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i < args.length; i++) {
+ File f = domain.getFile(args[i]);
+ if (f.exists() && f.getParentFile().exists()) {
+ sb.append(del);
+ sb.append(f.getName());
+ del = ",";
+ }
+ }
+ return sb.toString();
+ }
+
+ }
+
+ public String _isfile(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${isfile;...}");
+ return null;
+ }
+ else {
+ boolean isfile = true;
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ isfile &= f.isFile();
+ }
+ return isfile ? "true" : "false";
+ }
+
+ }
+
+ public String _isdir(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${isdir;...}");
+ return null;
+ }
+ else {
+ boolean isdir = true;
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ isdir &= f.isDirectory();
+ }
+ return isdir ? "true" : "false";
+ }
+
+ }
+
+ public String _tstamp(String args[]) {
+ String format = "yyyyMMddHHmm";
+ long now = System.currentTimeMillis();
+
+ if (args.length > 1) {
+ format = args[1];
+ if (args.length > 2) {
+ now = Long.parseLong(args[2]);
+ if (args.length > 3) {
+ domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
+ }
+ }
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ return sdf.format(new Date(now));
+ }
+
+ /**
+ * Wildcard a directory. The lists can contain Instruction that are matched
+ * against the given directory
+ *
+ * ${lsr;<dir>;<list>(;<list>)*} ${lsa;<dir>;<list>(;<list>)*}
+ *
+ * @author aqute
+ *
+ */
+
+ public String _lsr(String args[]) {
+ return ls(args, true);
+ }
+
+ public String _lsa(String args[]) {
+ return ls(args, false);
+ }
+
+ String ls(String args[], boolean relative) {
+ if (args.length < 2)
+ throw new IllegalArgumentException(
+ "the ${ls} macro must at least have a directory as parameter");
+
+ File dir = domain.getFile(args[1]);
+ if (!dir.isAbsolute())
+ throw new IllegalArgumentException(
+ "the ${ls} macro directory parameter is not absolute: " + dir);
+
+ if (!dir.exists())
+ throw new IllegalArgumentException(
+ "the ${ls} macro directory parameter does not exist: " + dir);
+
+ if (!dir.isDirectory())
+ throw new IllegalArgumentException(
+ "the ${ls} macro directory parameter points to a file instead of a directory: "
+ + dir);
+
+ List<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));
+
+ for (int i = 2; i < args.length; i++) {
+ Instructions filters = new Instructions(args[i]);
+ filters.select(files, true);
+ }
+
+ List<String> result = new ArrayList<String>();
+ for (File file : files)
+ result.add(relative ? file.getName() : file.getAbsolutePath());
+
+ return Processor.join(result, ",");
+ }
+
+ public String _currenttime(String args[]) {
+ return Long.toString(System.currentTimeMillis());
+ }
+
+ /**
+ * Modify a version to set a version policy. Thed policy is a mask that is
+ * mapped to a version.
+ *
+ * <pre>
+ * + increment
+ * - decrement
+ * = maintain
+ * ˜ discard
+ *
+ * ==+ = maintain major, minor, increment micro, discard qualifier
+ * ˜˜˜= = just get the qualifier
+ * version="[${version;==;${@}},${version;=+;${@}})"
+ * </pre>
+ *
+ *
+ *
+ *
+ * @param args
+ * @return
+ */
+ final static String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";
+ final static Pattern MASK = Pattern.compile(MASK_STRING);
+ final static String _versionHelp = "${version;<mask>;<version>}, modify a version\n"
+ + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
+ + "M ::= '+' | '-' | MQ\n"
+ + "MQ ::= '~' | '='";
+ final static Pattern _versionPattern[] = new Pattern[] {null, null, MASK, Verifier.VERSION};
+
+ public String _version(String args[]) {
+ verifyCommand(args, _versionHelp, null, 2, 3);
+
+ String mask = args[1];
+
+ Version version = null;
+ if (args.length >= 3)
+ version = new Version(args[2]);
+
+ return version(version, mask);
+ }
+
+ String version(Version version, String mask) {
+ if (version == null) {
+ String v = domain.getProperty("@");
+ if (v == null) {
+ domain.error(
+ "No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
+ mask);
+ v = "0";
+ }
+ version = new Version(v);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+
+ for (int i = 0; i < mask.length(); i++) {
+ char c = mask.charAt(i);
+ String result = null;
+ if (c != '~') {
+ if (i == 3) {
+ result = version.getQualifier();
+ }
+ else
+ if (Character.isDigit(c)) {
+ // Handle masks like +00, =+0
+ result = String.valueOf(c);
+ }
+ else {
+ int x = version.get(i);
+ switch (c) {
+ case '+' :
+ x++;
+ break;
+ case '-' :
+ x--;
+ break;
+ case '=' :
+ break;
+ }
+ result = Integer.toString(x);
+ }
+ if (result != null) {
+ sb.append(del);
+ del = ".";
+ sb.append(result);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Schortcut for version policy
+ *
+ * <pre>
+ * -provide-policy : ${policy;[==,=+)}
+ * -consume-policy : ${policy;[==,+)}
+ * </pre>
+ *
+ * @param args
+ * @return
+ */
+
+ static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),("
+ + MASK_STRING + ")(\\]|\\))");
+ static String _rangeHelp = "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
+ + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
+ + "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
+ static Pattern _rangePattern[] = new Pattern[] {null, RANGE_MASK};
+
+ public String _range(String args[]) {
+ verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
+ Version version = null;
+ if (args.length >= 3)
+ version = new Version(args[2]);
+ else {
+ String v = domain.getProperty("@");
+ if (v == null)
+ return null;
+ version = new Version(v);
+ }
+ String spec = args[1];
+
+ Matcher m = RANGE_MASK.matcher(spec);
+ m.matches();
+ String floor = m.group(1);
+ String floorMask = m.group(2);
+ String ceilingMask = m.group(3);
+ String ceiling = m.group(4);
+
+ String left = version(version, floorMask);
+ String right = version(version, ceilingMask);
+ StringBuilder sb = new StringBuilder();
+ sb.append(floor);
+ sb.append(left);
+ sb.append(",");
+ sb.append(right);
+ sb.append(ceiling);
+
+ String s = sb.toString();
+ VersionRange vr = new VersionRange(s);
+ if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
+ domain.error("${range} macro created an invalid range %s from %s and mask %s", s,
+ version, spec);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * System command. Execute a command and insert the result.
+ *
+ * @param args
+ * @param help
+ * @param patterns
+ * @param low
+ * @param high
+ */
+ public String system_internal(boolean allowFail, String args[]) throws Exception {
+ verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
+ + ";<command>[;<in>]}, execute a system command", null, 2, 3);
+ String command = args[1];
+ String input = null;
+
+ if (args.length > 2) {
+ input = args[2];
+ }
+
+ Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
+ if (input != null) {
+ process.getOutputStream().write(input.getBytes("UTF-8"));
+ }
+ process.getOutputStream().close();
+
+ String s = IO.collect(process.getInputStream(), "UTF-8");
+ int exitValue = process.waitFor();
+ if (exitValue != 0)
+ return exitValue + "";
+
+ if (!allowFail && (exitValue != 0)) {
+ domain.error("System command " + command + " failed with " + exitValue);
+ }
+ return s.trim();
+ }
+
+ public String _system(String args[]) throws Exception {
+ return system_internal(false, args);
+ }
+
+ public String _system_allow_fail(String args[]) throws Exception {
+ String result = "";
+ try {
+ result = system_internal(true, args);
+ }
+ catch (Throwable t) {
+ /* ignore */
+ }
+ return result;
+ }
+
+ public String _env(String args[]) {
+ verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);
+
+ try {
+ return System.getenv(args[1]);
+ }
+ catch (Throwable t) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the contents of a file.
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ */
+
+ public String _cat(String args[]) throws IOException {
+ verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
+ File f = domain.getFile(args[1]);
+ if (f.isFile()) {
+ return IO.collect(f);
+ }
+ else
+ if (f.isDirectory()) {
+ return Arrays.toString(f.list());
+ }
+ else {
+ try {
+ URL url = new URL(args[1]);
+ return IO.collect(url, "UTF-8");
+ }
+ catch (MalformedURLException mfue) {
+ // Ignore here
+ }
+ return null;
+ }
+ }
+
+ public static void verifyCommand(String args[], String help, Pattern[] patterns, int low,
+ int high) {
+ String message = "";
+ if (args.length > high) {
+ message = "too many arguments";
+ }
+ else
+ if (args.length < low) {
+ message = "too few arguments";
+ }
+ else {
+ for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
+ if (patterns[i] != null) {
+ Matcher m = patterns[i].matcher(args[i]);
+ if (!m.matches())
+ message += String.format("Argument %s (%s) does not match %s\n", i,
+ args[i], patterns[i].pattern());
+ }
+ }
+ }
+ if (message.length() != 0) {
+ StringBuilder sb = new StringBuilder();
+ String del = "${";
+ for (String arg : args) {
+ sb.append(del);
+ sb.append(arg);
+ del = ";";
+ }
+ sb.append("}, is not understood. ");
+ sb.append(message);
+ throw new IllegalArgumentException(sb.toString());
+ }
+ }
+
+ // Helper class to track expansion of variables
+ // on the stack.
+ static class Link {
+ Link previous;
+ String key;
+ Processor start;
+
+ public Link(Processor start, Link previous, String key) {
+ this.previous = previous;
+ this.key = key;
+ this.start = start;
+ }
+
+ public boolean contains(String key) {
+ if (this.key.equals(key))
+ return true;
+
+ if (previous == null)
+ return false;
+
+ return previous.contains(key);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ String del = "[";
+ for (Link r = this; r != null; r = r.previous) {
+ sb.append(del);
+ sb.append(r.key);
+ del = ",";
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Take all the properties and translate them to actual values. This method
+ * takes the set properties and traverse them over all entries, including
+ * the default properties for that properties. The values no longer contain
+ * macros.
+ *
+ * @return A new Properties with the flattened values
+ */
+ public Properties getFlattenedProperties() {
+ // Some macros only work in a lower processor, so we
+ // do not report unknown macros while flattening
+ flattening = true;
+ try {
+ Properties flattened = new Properties();
+ Properties source = domain.getProperties();
+ for (Enumeration< ? > e = source.propertyNames(); e.hasMoreElements();) {
+ String key = (String) e.nextElement();
+ if (!key.startsWith("_"))
+ if (key.startsWith("-"))
+ flattened.put(key, source.getProperty(key));
+ else
+ flattened.put(key, process(source.getProperty(key)));
+ }
+ return flattened;
+ }
+ finally {
+ flattening = false;
+ }
+ }
+
+ public final static String _fileHelp = "${file;<base>;<paths>...}, create correct OS dependent path";
+
+ public String _osfile(String args[]) {
+ verifyCommand(args, _fileHelp, null, 3, 3);
+ File base = new File(args[1]);
+ File f = Processor.getFile(base, args[2]);
+ return f.getAbsolutePath();
+ }
+
+ public String _path(String args[]) {
+ List<String> list = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ list.addAll(Processor.split(args[i]));
+ }
+ return Processor.join(list, File.pathSeparator);
+ }
+
+ public static Properties getParent(Properties p) {
+ try {
+ Field f = Properties.class.getDeclaredField("defaults");
+ f.setAccessible(true);
+ return (Properties) f.get(p);
+ }
+ catch (Exception e) {
+ Field[] fields = Properties.class.getFields();
+ System.err.println(Arrays.toString(fields));
+ return null;
+ }
+ }
+
+ public String process(String line) {
+ return process(line, domain);
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/OpCodes.java b/bundleplugin/src/main/java/aQute/lib/osgi/OpCodes.java
new file mode 100755
index 0000000..f0d3134
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/OpCodes.java
@@ -0,0 +1,1196 @@
+package aQute.lib.osgi;
+
+public class OpCodes {
+ final static short nop = 0x00; // [No change] performs
+ // no
+ // operation
+ final static short aconst_null = 0x01; // ? null pushes a null
+ // reference onto the stack
+ final static short iconst_m1 = 0x02; // ? -1 loads the int
+ // value -1
+ // onto the stack
+ final static short iconst_0 = 0x03; // ? 0 loads the int
+ // value 0
+ // onto the stack
+ final static short iconst_1 = 0x04; // ? 1 loads the int
+ // value 1
+ // onto the stack
+ final static short iconst_2 = 0x05; // ? 2 loads the int
+ // value 2
+ // onto the stack
+ final static short iconst_3 = 0x06; // ? 3 loads the int
+ // value 3
+ // onto the stack
+ final static short iconst_4 = 0x07; // ? 4 loads the int
+ // value 4
+ // onto the stack
+ final static short iconst_5 = 0x08; // ? 5 loads the int
+ // value 5
+ // onto the stack
+ final static short lconst_0 = 0x09; // ? 0L pushes the long
+ // 0 onto
+ // the stack
+ final static short bipush = 0x10; // byte ? value pushes a
+ // byte
+ // onto the stack as an integer
+ // value
+ final static short sipush = 0x11; // byte1, byte2 ? value
+ // pushes a
+ // signed integer (byte1 << 8 +
+ // byte2) onto the stack
+ final static short ldc = 0x12; // index ? value pushes
+ // a
+ // constant #index from a
+ // constant pool (String, int,
+ // float or class type) onto the
+ // stack
+ final static short ldc_w = 0x13; // indexbyte1,
+ // indexbyte2 ?
+ // value pushes a constant
+ // #index from a constant pool
+ // (String, int, float or class
+ // type) onto the stack (wide
+ // index is constructed as
+ // indexbyte1 << 8 + indexbyte2)
+ final static short ldc2_w = 0x14; // indexbyte1,
+ // indexbyte2 ?
+ // value pushes a constant
+ // #index from a constant pool
+ // (double or long) onto the
+ // stack (wide index is
+ // constructed as indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short iload = 0x15; // index ? value loads
+ // an int
+ // value from a variable #index
+ final static short lload = 0x16; // index ? value load a
+ // long
+ // value from a local variable
+ // #index
+ final static short fload = 0x17; // index ? value loads a
+ // float
+ // value from a local variable
+ // #index
+ final static short dload = 0x18; // index ? value loads a
+ // double
+ // value from a local variable
+ // #index
+ final static short aload = 0x19; // index ? objectref
+ // loads a
+ // reference onto the stack from
+ // a local variable #index
+ final static short lload_2 = 0x20; // ? value load a long
+ // value
+ // from a local variable 2
+ final static short lload_3 = 0x21; // ? value load a long
+ // value
+ // from a local variable 3
+ final static short fload_0 = 0x22; // ? value loads a float
+ // value
+ // from local variable 0
+ final static short fload_1 = 0x23; // ? value loads a float
+ // value
+ // from local variable 1
+ final static short fload_2 = 0x24; // ? value loads a float
+ // value
+ // from local variable 2
+ final static short fload_3 = 0x25; // ? value loads a float
+ // value
+ // from local variable 3
+ final static short dload_0 = 0x26; // ? value loads a
+ // double from
+ // local variable 0
+ final static short dload_1 = 0x27; // ? value loads a
+ // double from
+ // local variable 1
+ final static short dload_2 = 0x28; // ? value loads a
+ // double from
+ // local variable 2
+ final static short dload_3 = 0x29; // ? value loads a
+ // double from
+ // local variable 3
+ final static short faload = 0x30; // arrayref, index ?
+ // value loads
+ // a float from an array
+ final static short daload = 0x31; // arrayref, index ?
+ // value loads
+ // a double from an array
+ final static short aaload = 0x32; // arrayref, index ?
+ // value loads
+ // onto the stack a reference
+ // from an array
+ final static short baload = 0x33; // arrayref, index ?
+ // value loads
+ // a byte or Boolean value from
+ // an array
+ final static short caload = 0x34; // arrayref, index ?
+ // value loads
+ // a char from an array
+ final static short saload = 0x35; // arrayref, index ?
+ // value load
+ // short from array
+ final static short istore = 0x36; // index value ? store
+ // int value
+ // into variable #index
+ final static short lstore = 0x37; // index value ? store a
+ // long
+ // value in a local variable
+ // #index
+ final static short fstore = 0x38; // index value ? stores
+ // a float
+ // value into a local variable
+ // #index
+ final static short dstore = 0x39; // index value ? stores
+ // a double
+ // value into a local variable
+ // #index
+ final static short lstore_1 = 0x40; // value ? store a long
+ // value in
+ // a local variable 1
+ final static short lstore_2 = 0x41; // value ? store a long
+ // value in
+ // a local variable 2
+ final static short lstore_3 = 0x42; // value ? store a long
+ // value in
+ // a local variable 3
+ final static short fstore_0 = 0x43; // value ? stores a
+ // float value
+ // into local variable 0
+ final static short fstore_1 = 0x44; // value ? stores a
+ // float value
+ // into local variable 1
+ final static short fstore_2 = 0x45; // value ? stores a
+ // float value
+ // into local variable 2
+ final static short fstore_3 = 0x46; // value ? stores a
+ // float value
+ // into local variable 3
+ final static short dstore_0 = 0x47; // value ? stores a
+ // double into
+ // local variable 0
+ final static short dstore_1 = 0x48; // value ? stores a
+ // double into
+ // local variable 1
+ final static short dstore_2 = 0x49; // value ? stores a
+ // double into
+ // local variable 2
+ final static short lastore = 0x50; // arrayref, index,
+ // value ?
+ // store a long to an array
+ final static short fastore = 0x51; // arreyref, index,
+ // value ?
+ // stores a float in an array
+ final static short dastore = 0x52; // arrayref, index,
+ // value ?
+ // stores a double into an array
+ final static short aastore = 0x53; // arrayref, index,
+ // value ?
+ // stores into a reference to an
+ // array
+ final static short bastore = 0x54; // arrayref, index,
+ // value ?
+ // stores a byte or Boolean
+ // value into an array
+ final static short castore = 0x55; // arrayref, index,
+ // value ?
+ // stores a char into an array
+ final static short sastore = 0x56; // arrayref, index,
+ // value ?
+ // store short to array
+ final static short pop = 0x57; // value ? discards the
+ // top
+ // value on the stack
+ final static short pop2 = 0x58; // {value2, value1} ?
+ // discards
+ // the top two values on the
+ // stack (or one value, if it is
+ // a double or long)
+ final static short dup = 0x59; // value ? value, value
+ // duplicates the value on top
+ // of the stack
+ final static short iadd = 0x60; // value1, value2 ?
+ // result adds
+ // two ints together
+ final static short ladd = 0x61; // value1, value2 ?
+ // result add
+ // two longs
+ final static short fadd = 0x62; // value1, value2 ?
+ // result adds
+ // two floats
+ final static short dadd = 0x63; // value1, value2 ?
+ // result adds
+ // two doubles
+ final static short isub = 0x64; // value1, value2 ?
+ // result int
+ // subtract
+ final static short lsub = 0x65; // value1, value2 ?
+ // result
+ // subtract two longs
+ final static short fsub = 0x66; // value1, value2 ?
+ // result
+ // subtracts two floats
+ final static short dsub = 0x67; // value1, value2 ?
+ // result
+ // subtracts a double from
+ // another
+ final static short imul = 0x68; // value1, value2 ?
+ // result
+ // multiply two integers
+ final static short lmul = 0x69; // value1, value2 ?
+ // result
+ // multiplies two longs
+ final static short irem = 0x70; // value1, value2 ?
+ // result
+ // logical int remainder
+ final static short lrem = 0x71; // value1, value2 ?
+ // result
+ // remainder of division of two
+ // longs
+ final static short frem = 0x72; // value1, value2 ?
+ // result gets
+ // the remainder from a division
+ // between two floats
+ final static short drem = 0x73; // value1, value2 ?
+ // result gets
+ // the remainder from a division
+ // between two doubles
+ final static short ineg = 0x74; // value ? result negate
+ // int
+ final static short lneg = 0x75; // value ? result
+ // negates a long
+ final static short fneg = 0x76; // value ? result
+ // negates a
+ // float
+ final static short dneg = 0x77; // value ? result
+ // negates a
+ // double
+ final static short ishl = 0x78; // value1, value2 ?
+ // result int
+ // shift left
+ final static short lshl = 0x79; // value1, value2 ?
+ // result
+ // bitwise shift left of a long
+ // value1 by value2 positions
+ final static short ior = 0x80; // value1, value2 ?
+ // result
+ // logical int or
+ final static short lor = 0x81; // value1, value2 ?
+ // result
+ // bitwise or of two longs
+ final static short ixor = 0x82; // value1, value2 ?
+ // result int
+ // xor
+ final static short lxor = 0x83; // value1, value2 ?
+ // result
+ // bitwise exclusive or of two
+ // longs
+ final static short iinc = 0x84; // index, const [No
+ // change]
+ // increment local variable
+ // #index by signed byte const
+ final static short i2l = 0x85; // value ? result
+ // converts an
+ // int into a long
+ final static short i2f = 0x86; // value ? result
+ // converts an
+ // int into a float
+ final static short i2d = 0x87; // value ? result
+ // converts an
+ // int into a double
+ final static short l2i = 0x88; // value ? result
+ // converts a
+ // long to an int
+ final static short l2f = 0x89; // value ? result
+ // converts a
+ // long to a float
+ final static short d2f = 0x90; // value ? result
+ // converts a
+ // double to a float
+ final static short i2b = 0x91; // value ? result
+ // converts an
+ // int into a byte
+ final static short i2c = 0x92; // value ? result
+ // converts an
+ // int into a character
+ final static short i2s = 0x93; // value ? result
+ // converts an
+ // int into a short
+ final static short lcmp = 0x94; // value1, value2 ?
+ // result
+ // compares two longs values
+ final static short fcmpl = 0x95; // value1, value2 ?
+ // result
+ // compares two floats
+ final static short fcmpg = 0x96; // value1, value2 ?
+ // result
+ // compares two floats
+ final static short dcmpl = 0x97; // value1, value2 ?
+ // result
+ // compares two doubles
+ final static short dcmpg = 0x98; // value1, value2 ?
+ // result
+ // compares two doubles
+ final static short ifeq = 0x99; // branchbyte1,
+ // branchbyte2
+ // value ? if value is 0, branch
+ // to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short lconst_1 = 0x0a; // ? 1L pushes the long
+ // 1 onto
+ // the stack
+ final static short fconst_0 = 0x0b; // ? 0.0f pushes 0.0f on
+ // the
+ // stack
+ final static short fconst_1 = 0x0c; // ? 1.0f pushes 1.0f on
+ // the
+ // stack
+ final static short fconst_2 = 0x0d; // ? 2.0f pushes 2.0f on
+ // the
+ // stack
+ final static short dconst_0 = 0x0e; // ? 0.0 pushes the
+ // constant 0.0
+ // onto the stack
+ final static short dconst_1 = 0x0f; // ? 1.0 pushes the
+ // constant 1.0
+ // onto the stack
+ final static short iload_0 = 0x1a; // ? value loads an int
+ // value
+ // from variable 0
+ final static short iload_1 = 0x1b; // ? value loads an int
+ // value
+ // from variable 1
+ final static short iload_2 = 0x1c; // ? value loads an int
+ // value
+ // from variable 2
+ final static short iload_3 = 0x1d; // ? value loads an int
+ // value
+ // from variable 3
+ final static short lload_0 = 0x1e; // ? value load a long
+ // value
+ // from a local variable 0
+ final static short lload_1 = 0x1f; // ? value load a long
+ // value
+ // from a local variable 1
+ final static short aload_0 = 0x2a; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 0
+ final static short aload_1 = 0x2b; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 1
+ final static short aload_2 = 0x2c; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 2
+ final static short aload_3 = 0x2d; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 3
+ final static short iaload = 0x2e; // arrayref, index ?
+ // value loads
+ // an int from an array
+ final static short laload = 0x2f; // arrayref, index ?
+ // value load
+ // a long from an array
+ final static short astore = 0x3a; // index objectref ?
+ // stores a
+ // reference into a local
+ // variable #index
+ final static short istore_0 = 0x3b; // value ? store int
+ // value into
+ // variable 0
+ final static short istore_1 = 0x3c; // value ? store int
+ // value into
+ // variable 1
+ final static short istore_2 = 0x3d; // value ? store int
+ // value into
+ // variable 2
+ final static short istore_3 = 0x3e; // value ? store int
+ // value into
+ // variable 3
+ final static short lstore_0 = 0x3f; // value ? store a long
+ // value in
+ // a local variable 0
+ final static short dstore_3 = 0x4a; // value ? stores a
+ // double into
+ // local variable 3
+ final static short astore_0 = 0x4b; // objectref ? stores a
+ // reference into local variable
+ // 0
+ final static short astore_1 = 0x4c; // objectref ? stores a
+ // reference into local variable
+ // 1
+ final static short astore_2 = 0x4d; // objectref ? stores a
+ // reference into local variable
+ // 2
+ final static short astore_3 = 0x4e; // objectref ? stores a
+ // reference into local variable
+ // 3
+ final static short iastore = 0x4f; // arrayref, index,
+ // value ?
+ // stores an int into an array
+ final static short dup_x1 = 0x5a; // value2, value1 ?
+ // value1,
+ // value2, value1 inserts a copy
+ // of the top value into the
+ // stack two values from the top
+ final static short dup_x2 = 0x5b; // value3, value2,
+ // value1 ?
+ // value1, value3, value2,
+ // value1 inserts a copy of the
+ // top value into the stack two
+ // (if value2 is double or long
+ // it takes up the entry of
+ // value3, too) or three values
+ // (if value2 is neither double
+ // nor long) from the top
+ final static short dup2 = 0x5c; // {value2, value1} ?
+ // {value2,
+ // value1}, {value2, value1}
+ // duplicate top two stack words
+ // (two values, if value1 is not
+ // double nor long; a single
+ // value, if value1 is double or
+ // long)
+ final static short dup2_x1 = 0x5d; // value3, {value2,
+ // value1} ?
+ // {value2, value1}, value3,
+ // {value2, value1} duplicate
+ // two words and insert beneath
+ // third word (see explanation
+ // above)
+ final static short dup2_x2 = 0x5e; // {value4, value3},
+ // {value2,
+ // value1} ? {value2, value1},
+ // {value4, value3}, {value2,
+ // value1} duplicate two words
+ // and insert beneath fourth
+ // word
+ final static short swap = 0x5f; // value2, value1 ?
+ // value1,
+ // value2 swaps two top words on
+ // the stack (note that value1
+ // and value2 must not be double
+ // or long)
+ final static short fmul = 0x6a; // value1, value2 ?
+ // result
+ // multiplies two floats
+ final static short dmul = 0x6b; // value1, value2 ?
+ // result
+ // multiplies two doubles
+ final static short idiv = 0x6c; // value1, value2 ?
+ // result
+ // divides two integers
+ final static short ldiv = 0x6d; // value1, value2 ?
+ // result
+ // divide two longs
+ final static short fdiv = 0x6e; // value1, value2 ?
+ // result
+ // divides two floats
+ final static short ddiv = 0x6f; // value1, value2 ?
+ // result
+ // divides two doubles
+ final static short ishr = 0x7a; // value1, value2 ?
+ // result int
+ // shift right
+ final static short lshr = 0x7b; // value1, value2 ?
+ // result
+ // bitwise shift right of a long
+ // value1 by value2 positions
+ final static short iushr = 0x7c; // value1, value2 ?
+ // result int
+ // shift right
+ final static short lushr = 0x7d; // value1, value2 ?
+ // result
+ // bitwise shift right of a long
+ // value1 by value2 positions,
+ // unsigned
+ final static short iand = 0x7e; // value1, value2 ?
+ // result
+ // performs a logical and on two
+ // integers
+ final static short land = 0x7f; // value1, value2 ?
+ // result
+ // bitwise and of two longs
+ final static short l2d = 0x8a; // value ? result
+ // converts a
+ // long to a double
+ final static short f2i = 0x8b; // value ? result
+ // converts a
+ // float to an int
+ final static short f2l = 0x8c; // value ? result
+ // converts a
+ // float to a long
+ final static short f2d = 0x8d; // value ? result
+ // converts a
+ // float to a double
+ final static short d2i = 0x8e; // value ? result
+ // converts a
+ // double to an int
+ final static short d2l = 0x8f; // value ? result
+ // converts a
+ // double to a long
+ final static short ifne = 0x9a; // branchbyte1,
+ // branchbyte2
+ // value ? if value is not 0,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short iflt = 0x9b; // branchbyte1,
+ // branchbyte2
+ // value ? if value is less than
+ // 0, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifge = 0x9c; // branchbyte1,
+ // branchbyte2
+ // value ? if value is greater
+ // than or equal to 0, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifgt = 0x9d; // branchbyte1,
+ // branchbyte2
+ // value ? if value is greater
+ // than 0, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifle = 0x9e; // branchbyte1,
+ // branchbyte2
+ // value ? if value is less than
+ // or equal to 0, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpeq = 0x9f; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if ints are
+ // equal, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpne = 0xa0; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if ints are
+ // not equal, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmplt = 0xa1; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // less than value2, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpge = 0xa2; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // greater than or equal to
+ // value2, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpgt = 0xa3; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // greater than value2, branch
+ // to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmple = 0xa4; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // less than or equal to value2,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_acmpeq = 0xa5; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if
+ // references are equal, branch
+ // to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_acmpne = 0xa6; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if
+ // references are not equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short goto_ = 0xa7; // branchbyte1,
+ // branchbyte2 [no
+ // change] goes to another
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short jsr = 0xa8; // branchbyte1,
+ // branchbyte2 ?
+ // address jump to subroutine at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2) and place the
+ // return address on the stack
+ final static short ret = 0xa9; // index [No change]
+ // continue
+ // execution from address taken
+ // from a local variable #index
+ // (the asymmetry with jsr is
+ // intentional)
+ final static short tableswitch = 0xaa; // [0-3 bytes padding],
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // lowbyte1, lowbyte2, lowbyte3,
+ // lowbyte4, highbyte1,
+ // highbyte2, highbyte3,
+ // highbyte4, jump offsets...
+ // index ? continue execution
+ // from an address in the table
+ // at offset index
+ final static short lookupswitch = 0xab; // <0-3 bytes padding>,
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // npairs1, npairs2, npairs3,
+ // npairs4, match-offset
+ // pairs... key ? a target
+ // address is looked up from a
+ // table using a key and
+ // execution continues from the
+ // instruction at that address
+ final static short ireturn = 0xac; // value ? [empty]
+ // returns an
+ // integer from a method
+ final static short lreturn = 0xad; // value ? [empty]
+ // returns a
+ // long value
+ final static short freturn = 0xae; // value ? [empty]
+ // returns a
+ // float
+ final static short dreturn = 0xaf; // value ? [empty]
+ // returns a
+ // double from a method
+ final static short areturn = 0xb0; // objectref ? [empty]
+ // returns a
+ // reference from a method
+ final static short return_ = 0xb1; // ? [empty] return void
+ // from
+ // method
+ final static short getstatic = 0xb2; // index1, index2 ?
+ // value gets a
+ // static field value of a
+ // class, where the field is
+ // identified by field reference
+ // in the constant pool index
+ // (index1 << 8 + index2)
+ final static short putstatic = 0xb3; // indexbyte1,
+ // indexbyte2 value
+ // ? set static field to value
+ // in a class, where the field
+ // is identified by a field
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short getfield = 0xb4; // index1, index2
+ // objectref ?
+ // value gets a field value of
+ // an object objectref, where
+ // the field is identified by
+ // field reference in the
+ // constant pool index (index1
+ // << 8 + index2)
+ final static short putfield = 0xb5; // indexbyte1,
+ // indexbyte2
+ // objectref, value ? set field
+ // to value in an object
+ // objectref, where the field is
+ // identified by a field
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short invokevirtual = 0xb6; // indexbyte1,
+ // indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke virtual method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short invokespecial = 0xb7; // indexbyte1,
+ // indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke instance method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short invokestatic = 0xb8; // indexbyte1,
+ // indexbyte2 [arg1,
+ // arg2, ...] ? invoke a static
+ // method, where the method is
+ // identified by method
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short invokeinterface = 0xb9; // indexbyte1,
+ // indexbyte2,
+ // count, 0 objectref, [arg1,
+ // arg2, ...] ? invokes an
+ // interface method on object
+ // objectref, where the
+ // interface method is
+ // identified by method
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short xxxunusedxxx = 0xba; // this opcode is
+ // reserved "for
+ // historical reasons"
+ final static short new_ = 0xbb; // indexbyte1,
+ // indexbyte2 ?
+ // objectref creates new object
+ // of type identified by class
+ // reference in constant pool
+ // index (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short newarray = 0xbc; // atype count ?
+ // arrayref
+ // creates new array with count
+ // elements of primitive type
+ // identified by atype
+ final static short anewarray = 0xbd; // indexbyte1,
+ // indexbyte2 count
+ // ? arrayref creates a new
+ // array of references of length
+ // count and component type
+ // identified by the class
+ // reference index (indexbyte1
+ // << 8 + indexbyte2) in the
+ // constant pool
+ final static short arraylength = 0xbe; // arrayref ? length
+ // gets the
+ // length of an array
+ final static short athrow = 0xbf; // objectref ? [empty],
+ // objectref throws an error or
+ // exception (notice that the
+ // rest of the stack is cleared,
+ // leaving only a reference to
+ // the Throwable)
+ final static short checkcast = 0xc0; // indexbyte1,
+ // indexbyte2
+ // objectref ? objectref checks
+ // whether an objectref is of a
+ // certain type, the class
+ // reference of which is in the
+ // constant pool at index
+ // (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short instanceof_ = 0xc1; // indexbyte1,
+ // indexbyte2
+ // objectref ? result determines
+ // if an object objectref is of
+ // a given type, identified by
+ // class reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short monitorenter = 0xc2; // objectref ? enter
+ // monitor for
+ // object ("grab the lock" -
+ // start of synchronized()
+ // section)
+ final static short monitorexit = 0xc3; // objectref ? exit
+ // monitor for
+ // object ("release the lock" -
+ // end of synchronized()
+ // section)
+ final static short wide = 0xc4; // opcode, indexbyte1,
+ // indexbyte2
+ final static short multianewarray = 0xc5; // indexbyte1,
+ // indexbyte2,
+ // dimensions count1,
+ // [count2,...] ? arrayref
+ // create a new array of
+ // dimensions dimensions with
+ // elements of type identified
+ // by class reference in
+ // constant pool index
+ // (indexbyte1 << 8 +
+ // indexbyte2); the sizes of
+ // each dimension is identified
+ // by count1, [count2, etc]
+ final static short ifnull = 0xc6; // branchbyte1,
+ // branchbyte2
+ // value ? if value is null,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifnonnull = 0xc7; // branchbyte1,
+ // branchbyte2
+ // value ? if value is not null,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short goto_w = 0xc8; // branchbyte1,
+ // branchbyte2,
+ // branchbyte3, branchbyte4 [no
+ // change] goes to another
+ // instruction at branchoffset
+ // (signed int constructed from
+ // unsigned bytes branchbyte1 <<
+ // 24 + branchbyte2 << 16 +
+ // branchbyte3 << 8 +
+ // branchbyte4)
+ final static short jsr_w = 0xc9; // branchbyte1,
+ // branchbyte2,
+ // branchbyte3, branchbyte4 ?
+ // address jump to subroutine at
+ // branchoffset (signed int
+ // constructed from unsigned
+ // bytes branchbyte1 << 24 +
+ // branchbyte2 << 16 +
+ // branchbyte3 << 8 +
+ // branchbyte4) and place the
+ // return address on the stack
+ final static short breakpoint = 0xca; // reserved for
+ // breakpoints in
+ // Java debuggers; should not
+ // appear in any class file
+ final static short impdep1 = 0xfe; // reserved for
+ // implementation-dependent
+ // operations within debuggers;
+ // should not appear in any
+ // class file
+ final static short impdep2 = 0xff; // reserved for
+ // implementation-dependent
+ // operations within debuggers;
+ // should not appear in any
+ // class file
+
+ final static byte OFFSETS[] = new byte[256];
+
+ static {
+ OFFSETS[bipush] = 1; // byte ? value pushes a byte onto the
+ // stack as an integer value
+ OFFSETS[sipush] = 2; // byte1, byte2 ? value pushes a signed
+ // integer (byte1 << 8 + byte2) onto the
+ // stack
+ OFFSETS[ldc] = 1; // index ? value pushes a constant
+ // #index from a constant pool (String,
+ // int, float or class type) onto the
+ // stack
+ OFFSETS[ldc_w] = 2; // indexbyte1, indexbyte2 ? value pushes
+ // a constant #index from a constant
+ // pool (String, int, float or class
+ // type) onto the stack (wide index is
+ // constructed as indexbyte1 << 8 +
+ // indexbyte2)
+ OFFSETS[ldc2_w] = 2; // indexbyte1, indexbyte2 ? value pushes
+ // a constant #index from a constant
+ // pool (double or long) onto the stack
+ // (wide index is constructed as
+ // indexbyte1 << 8 + indexbyte2)
+ OFFSETS[iload] = 1; // index ? value loads an int value from
+ // a variable #index
+ OFFSETS[lload] = 1; // index ? value load a long value from
+ // a local variable #index
+ OFFSETS[fload] = 1; // index ? value loads a float value
+ // from a local variable #index
+ OFFSETS[dload] = 1; // index ? value loads a double value
+ // from a local variable #index
+ OFFSETS[aload] = 1; // index ? objectref loads a reference
+ // onto the stack from a local variable
+ // #index
+ OFFSETS[istore] = 1; // index value ? store int value into
+ // variable #index
+ OFFSETS[lstore] = 1; // index value ? store a long value in a
+ // local variable #index
+ OFFSETS[fstore] = 1; // index value ? stores a float value
+ // into a local variable #index
+ OFFSETS[dstore] = 1; // index value ? stores a double value
+ // into a local variable #index
+ OFFSETS[iinc] = 2; // index, const [No change] increment
+ // local variable #index by signed byte
+ // const
+ OFFSETS[ifeq] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is 0, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[astore] = 1; // index objectref ? stores a reference
+ // into a local variable #index
+ OFFSETS[ifne] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is not 0, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[iflt] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is less than 0, branch to
+ // instruction at branchoffset (signed
+ // short constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[ifge] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is greater than or equal to 0,
+ // branch to instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[ifgt] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is greater than 0, branch to
+ // instruction at branchoffset (signed
+ // short constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[ifle] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is less than or equal to 0,
+ // branch to instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_icmpeq] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if ints are equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_icmpne] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if ints are not equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_icmplt] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is less than
+ // value2, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_icmpge] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is greater
+ // than or equal to value2, branch
+ // to instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_icmpgt] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is greater
+ // than value2, branch to
+ // instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_icmple] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is less than
+ // or equal to value2, branch to
+ // instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_acmpeq] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if references are equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_acmpne] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if references are not
+ // equal, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[goto_] = 2; // branchbyte1, branchbyte2 [no change]
+ // goes to another instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[jsr] = 2; // branchbyte1, branchbyte2 ? address
+ // jump to subroutine at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2) and place the return
+ // address on the stack
+ OFFSETS[ret] = 1; // index [No change] continue execution
+ // from address taken from a local
+ // variable #index (the asymmetry with
+ // jsr is intentional)
+ OFFSETS[tableswitch] = -1; // [0-3 bytes padding],
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // lowbyte1, lowbyte2, lowbyte3,
+ // lowbyte4, highbyte1,
+ // highbyte2, highbyte3,
+ // highbyte4, jump offsets...
+ // index ? continue execution
+ // from an address in the table
+ // at offset index
+ OFFSETS[lookupswitch] = -1; // <0-3 bytes padding>,
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // npairs1, npairs2, npairs3,
+ // npairs4, match-offset
+ // pairs... key ? a target
+ // address is looked up from a
+ // table using a key and
+ // execution continues from the
+ // instruction at that address
+ OFFSETS[getstatic] = 2; // index1, index2 ? value gets a
+ // static field value of a class,
+ // where the field is identified by
+ // field reference in the constant
+ // pool index (index1 << 8 + index2)
+ OFFSETS[putstatic] = 2; // indexbyte1, indexbyte2 value ?
+ // set static field to value in a
+ // class, where the field is
+ // identified by a field reference
+ // index in constant pool
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[getfield] = 2; // index1, index2 objectref ? value
+ // gets a field value of an object
+ // objectref, where the field is
+ // identified by field reference in
+ // the constant pool index (index1
+ // << 8 + index2)
+ OFFSETS[putfield] = 2; // indexbyte1, indexbyte2 objectref,
+ // value ? set field to value in an
+ // object objectref, where the field
+ // is identified by a field
+ // reference index in constant pool
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[invokevirtual] = 2; // indexbyte1, indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke virtual method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ OFFSETS[invokespecial] = 2; // indexbyte1, indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke instance method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ OFFSETS[invokestatic] = 2; // indexbyte1, indexbyte2 [arg1,
+ // arg2, ...] ? invoke a static
+ // method, where the method is
+ // identified by method
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ OFFSETS[invokeinterface] = 2; // indexbyte1, indexbyte2,
+ // count, 0 objectref,
+ // [arg1, arg2, ...] ?
+ // invokes an interface
+ // method on object
+ // objectref, where the
+ // interface method is
+ // identified by method
+ // reference index in
+ // constant pool (indexbyte1
+ // << 8 + indexbyte2)
+ OFFSETS[new_] = 2; // indexbyte1, indexbyte2 ? objectref
+ // creates new object of type identified
+ // by class reference in constant pool
+ // index (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[newarray] = 1; // atype count ? arrayref creates
+ // new array with count elements of
+ // primitive type identified by
+ // atype
+ OFFSETS[anewarray] = 2; // indexbyte1, indexbyte2 count ?
+ // arrayref creates a new array of
+ // references of length count and
+ // component type identified by the
+ // class reference index (indexbyte1
+ // << 8 + indexbyte2) in the
+ // constant pool
+ OFFSETS[checkcast] = 2; // indexbyte1, indexbyte2 objectref
+ // ? objectref checks whether an
+ // objectref is of a certain type,
+ // the class reference of which is
+ // in the constant pool at index
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[instanceof_] = 2; // indexbyte1, indexbyte2 objectref
+ // ? result determines if an object
+ // objectref is of a given type,
+ // identified by class reference
+ // index in constant pool
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[wide] = 3; // opcode, indexbyte1, indexbyte2
+ OFFSETS[multianewarray] = 3; // indexbyte1, indexbyte2,
+ // dimensions count1,
+ // [count2,...] ? arrayref
+ // create a new array of
+ // dimensions dimensions with
+ // elements of type identified
+ // by class reference in
+ // constant pool index
+ // (indexbyte1 << 8 +
+ // indexbyte2); the sizes of
+ // each dimension is identified
+ // by count1, [count2, etc]
+ OFFSETS[ifnull] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is null, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[ifnonnull] = 2; // branchbyte1, branchbyte2 value ?
+ // if value is not null, branch to
+ // instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[goto_w] = 4; // branchbyte1, branchbyte2,
+ // branchbyte3, branchbyte4 [no change]
+ // goes to another instruction at
+ // branchoffset (signed int constructed
+ // from unsigned bytes branchbyte1 << 24
+ // + branchbyte2 << 16 + branchbyte3 <<
+ // 8 + branchbyte4)
+ OFFSETS[jsr_w] = 4; // branchbyte1, branchbyte2,
+ // branchbyte3, branchbyte4 ? address
+ // jump to subroutine at branchoffset
+ // (signed int constructed from unsigned
+ // bytes branchbyte1 << 24 + branchbyte2
+ // << 16 + branchbyte3 << 8 +
+ // branchbyte4) and place the return
+ // address on the stack
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Packages.java b/bundleplugin/src/main/java/aQute/lib/osgi/Packages.java
new file mode 100644
index 0000000..09e8305
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Packages.java
@@ -0,0 +1,230 @@
+package aQute.lib.osgi;
+
+import java.util.*;
+
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.libg.header.*;
+
+public class Packages implements Map<PackageRef, Attrs> {
+ private LinkedHashMap<PackageRef, Attrs> map;
+ static Map<PackageRef, Attrs> EMPTY = Collections.emptyMap();
+
+ public Packages(Packages other) {
+ if (other.map != null) {
+ map = new LinkedHashMap<Descriptors.PackageRef, Attrs>(other.map);
+ }
+ }
+
+ public Packages() {
+ }
+
+ public void clear() {
+ if(map!=null)
+ map.clear();
+ }
+
+ public boolean containsKey(PackageRef name) {
+ if (map == null)
+ return false;
+
+ return map.containsKey(name);
+ }
+
+ @Deprecated public boolean containsKey(Object name) {
+ assert name instanceof PackageRef;
+ if (map == null)
+ return false;
+
+ return map.containsKey((PackageRef) name);
+ }
+
+ public boolean containsValue(Attrs value) {
+ if (map == null)
+ return false;
+
+ return map.containsValue(value);
+ }
+
+ @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<PackageRef, Attrs>> entrySet() {
+ if (map == null)
+ return EMPTY.entrySet();
+
+ return map.entrySet();
+ }
+
+ @Deprecated public Attrs get(Object key) {
+ assert key instanceof PackageRef;
+ if (map == null)
+ return null;
+
+ return map.get((PackageRef) key);
+ }
+
+ public Attrs get(PackageRef key) {
+ if (map == null)
+ return null;
+
+ return map.get(key);
+ }
+
+ public boolean isEmpty() {
+ return map == null || map.isEmpty();
+ }
+
+ public Set<PackageRef> keySet() {
+ if (map == null)
+ return EMPTY.keySet();
+
+ return map.keySet();
+ }
+
+ public Attrs put(PackageRef ref) {
+ Attrs attrs = get(ref);
+ if (attrs != null)
+ return attrs;
+
+ attrs = new Attrs();
+ put(ref, attrs);
+ return attrs;
+ }
+
+ public Attrs put(PackageRef key, Attrs value) {
+ if (map == null)
+ map = new LinkedHashMap<PackageRef, Attrs>();
+
+ return map.put(key, value);
+ }
+
+ public void putAll(Map<? extends PackageRef, ? extends Attrs> map) {
+ if (this.map == null)
+ if (map.isEmpty())
+ return;
+ else
+ this.map = new LinkedHashMap<PackageRef, Attrs>();
+ this.map.putAll(map);
+ }
+
+ public void putAllIfAbsent(Map<PackageRef, ? extends Attrs> map) {
+ for(Map.Entry<PackageRef, ? extends Attrs> entry : map.entrySet() ) {
+ if ( !containsKey(entry.getKey()))
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Deprecated public Attrs remove(Object var0) {
+ assert var0 instanceof PackageRef;
+ if (map == null)
+ return null;
+
+ return map.remove((PackageRef) var0);
+ }
+
+ public Attrs remove(PackageRef 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 Attrs getByFQN(String s) {
+ if (map == null)
+ return null;
+
+ for (Map.Entry<PackageRef, Attrs> pr : map.entrySet()) {
+ if (pr.getKey().getFQN().equals(s))
+ return pr.getValue();
+ }
+ return null;
+ }
+
+ public Attrs getByBinaryName(String s) {
+ if (map == null)
+ return null;
+
+ for (Map.Entry<PackageRef, Attrs> pr : map.entrySet()) {
+ if (pr.getKey().getBinary().equals(s))
+ pr.getValue();
+ }
+ return null;
+ }
+
+ public boolean containsFQN(String s) {
+ return getByFQN(s) != null;
+ }
+
+ public boolean containsBinaryName(String s) {
+ return getByFQN(s) != null;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ append(sb);
+ return sb.toString();
+ }
+
+ public void append(StringBuilder sb) {
+ String del = "";
+ for (Map.Entry<PackageRef, Attrs> s : entrySet()) {
+ sb.append(del);
+ sb.append(s.getKey());
+ if (!s.getValue().isEmpty()) {
+ sb.append(';');
+ s.getValue().append(sb);
+ }
+ del = ",";
+ }
+ }
+
+ public void merge(PackageRef ref, boolean unique, Attrs... attrs) {
+ if ( unique ) {
+ while ( containsKey(ref))
+ ref = ref.getDuplicate();
+ }
+
+ Attrs org = put(ref);
+ for (Attrs a : attrs) {
+ if (a != null)
+ org.putAll(a);
+ }
+ }
+
+ public Attrs get(PackageRef packageRef, Attrs deflt) {
+ Attrs mine = get(packageRef);
+ if ( mine!=null)
+ return mine;
+
+ return deflt;
+ }
+
+
+ @Deprecated
+ public boolean equals(Object other) {
+ return super.equals(other);
+ }
+
+ @Deprecated
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/PreprocessResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/PreprocessResource.java
new file mode 100644
index 0000000..f003abc
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/PreprocessResource.java
@@ -0,0 +1,44 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public class PreprocessResource extends AbstractResource {
+ final Resource resource;
+ final Processor processor;
+
+ public PreprocessResource(Processor processor, Resource r) {
+ super(r.lastModified());
+ this.processor = processor;
+ this.resource = r;
+ setExtra(resource.getExtra());
+ }
+
+ protected byte[] getBytes() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(2000);
+ OutputStreamWriter osw = new OutputStreamWriter(bout, Constants.DEFAULT_CHARSET);
+ PrintWriter pw = new PrintWriter(osw);
+ InputStream in = null;
+ BufferedReader rdr = null;
+ try {
+ in = resource.openInputStream();
+ rdr = new BufferedReader(new InputStreamReader(in,"UTF8"));
+ String line = rdr.readLine();
+ while (line != null) {
+ line = processor.getReplacer().process(line);
+ pw.println(line);
+ line = rdr.readLine();
+ }
+ pw.flush();
+ byte [] data= bout.toByteArray();
+ return data;
+
+ } finally {
+ if (rdr != null) {
+ rdr.close();
+ }
+ if (in != null) {
+ in.close();
+ }
+ }
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Processor.java b/bundleplugin/src/main/java/aQute/lib/osgi/Processor.java
new file mode 100755
index 0000000..655ce4d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Processor.java
@@ -0,0 +1,1587 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.collections.*;
+import aQute.lib.io.*;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+
+public class Processor extends Domain implements Reporter, Registry, Constants, Closeable {
+
+ static ThreadLocal<Processor> current = new ThreadLocal<Processor>();
+ static ExecutorService executor = Executors.newCachedThreadPool();
+ static Random random = new Random();
+
+ // TODO handle include files out of date
+ // TODO make splitter skip eagerly whitespace so trim is not necessary
+ public final static String LIST_SPLITTER = "\\s*,\\s*";
+ final List<String> errors = new ArrayList<String>();
+ final List<String> warnings = new ArrayList<String>();
+ final Set<Object> basicPlugins = new HashSet<Object>();
+ private final Set<Closeable> toBeClosed = new HashSet<Closeable>();
+ Set<Object> plugins;
+
+ boolean pedantic;
+ boolean trace;
+ boolean exceptions;
+ boolean fileMustExist = true;
+
+ private File base = new File("").getAbsoluteFile();
+
+ Properties properties;
+ private Macro replacer;
+ private long lastModified;
+ private File propertiesFile;
+ private boolean fixup = true;
+ long modified;
+ Processor parent;
+ List<File> included;
+
+ CL pluginLoader;
+ Collection<String> filter;
+ HashSet<String> missingCommand;
+
+ public Processor() {
+ properties = new Properties();
+ }
+
+ public Processor(Properties parent) {
+ properties = new Properties(parent);
+ }
+
+ public Processor(Processor parent) {
+ this(parent.properties);
+ this.parent = parent;
+ }
+
+ public void setParent(Processor processor) {
+ this.parent = processor;
+ Properties ext = new Properties(processor.properties);
+ ext.putAll(this.properties);
+ this.properties = ext;
+ }
+
+ public Processor getParent() {
+ return parent;
+ }
+
+ public Processor getTop() {
+ if (parent == null)
+ return this;
+ else
+ return parent.getTop();
+ }
+
+ public void getInfo(Reporter processor, String prefix) {
+ if (isFailOk())
+ addAll(warnings, processor.getErrors(), prefix);
+ else
+ addAll(errors, processor.getErrors(), prefix);
+ addAll(warnings, processor.getWarnings(), prefix);
+
+ processor.getErrors().clear();
+ processor.getWarnings().clear();
+ }
+
+ public void getInfo(Reporter processor) {
+ getInfo(processor, "");
+ }
+
+ private <T> void addAll(List<String> to, List<? extends T> from, String prefix) {
+ for (T x : from) {
+ to.add(prefix + x);
+ }
+ }
+
+ /**
+ * A processor can mark itself current for a thread.
+ *
+ * @return
+ */
+ private Processor current() {
+ Processor p = current.get();
+ if (p == null)
+ return this;
+ else
+ return p;
+ }
+
+ public void warning(String string, Object... args) {
+ Processor p = current();
+ String s = formatArrays(string, args);
+ if (!p.warnings.contains(s))
+ p.warnings.add(s);
+ p.signal();
+ }
+
+ public void error(String string, Object... args) {
+ Processor p = current();
+ if (p.isFailOk())
+ p.warning(string, args);
+ else {
+ String s = formatArrays(string, args);
+ if (!p.errors.contains(s))
+ p.errors.add(s);
+ }
+ p.signal();
+ }
+
+ public void error(String string, Throwable t, Object... args) {
+ Processor p = current();
+
+ if (p.isFailOk())
+ p.warning(string + ": " + t, args);
+ else {
+ p.errors.add("Exception: " + t.getMessage());
+ String s = formatArrays(string, args);
+ if (!p.errors.contains(s))
+ p.errors.add(s);
+ }
+ if (p.exceptions)
+ t.printStackTrace();
+
+ p.signal();
+ }
+
+ public void signal() {
+ }
+
+ public List<String> getWarnings() {
+ return warnings;
+ }
+
+ public List<String> getErrors() {
+ return errors;
+ }
+
+ /**
+ * Standard OSGi header parser.
+ *
+ * @param value
+ * @return
+ */
+ static public Parameters parseHeader(String value, Processor logger) {
+ return new Parameters(value, logger);
+ }
+
+ public Parameters parseHeader(String value) {
+ return new Parameters(value, this);
+ }
+
+ public void addClose(Closeable jar) {
+ assert jar != null;
+ toBeClosed.add(jar);
+ }
+
+ public void removeClose(Closeable jar) {
+ assert jar != null;
+ toBeClosed.remove(jar);
+ }
+
+ public void progress(String s, Object... args) {
+ trace(s, args);
+ }
+
+ public boolean isPedantic() {
+ return current().pedantic;
+ }
+
+ public void setPedantic(boolean pedantic) {
+ this.pedantic = pedantic;
+ }
+
+ public void use(Processor reporter) {
+ setPedantic(reporter.isPedantic());
+ setTrace(reporter.isTrace());
+ setBase(reporter.getBase());
+ setFailOk(reporter.isFailOk());
+ }
+
+ public static File getFile(File base, String file) {
+ return IO.getFile(base, file);
+ }
+
+ public File getFile(String file) {
+ return getFile(base, file);
+ }
+
+ /**
+ * Return a list of plugins that implement the given class.
+ *
+ * @param clazz
+ * Each returned plugin implements this class/interface
+ * @return A list of plugins
+ */
+ public <T> List<T> getPlugins(Class<T> clazz) {
+ List<T> l = new ArrayList<T>();
+ Set<Object> all = getPlugins();
+ for (Object plugin : all) {
+ if (clazz.isInstance(plugin))
+ l.add(clazz.cast(plugin));
+ }
+ return l;
+ }
+
+ /**
+ * Returns the first plugin it can find of the given type.
+ *
+ * @param <T>
+ * @param clazz
+ * @return
+ */
+ public <T> T getPlugin(Class<T> clazz) {
+ Set<Object> all = getPlugins();
+ for (Object plugin : all) {
+ if (clazz.isInstance(plugin))
+ return clazz.cast(plugin);
+ }
+ return null;
+ }
+
+ /**
+ * Return a list of plugins. Plugins are defined with the -plugin command.
+ * They are class names, optionally associated with attributes. Plugins can
+ * implement the Plugin interface to see these attributes.
+ *
+ * Any object can be a plugin.
+ *
+ * @return
+ */
+ protected synchronized Set<Object> getPlugins() {
+ if (this.plugins != null)
+ return this.plugins;
+
+ missingCommand = new HashSet<String>();
+ Set<Object> list = new LinkedHashSet<Object>();
+
+ // The owner of the plugin is always in there.
+ list.add(this);
+ setTypeSpecificPlugins(list);
+
+ if (parent != null)
+ list.addAll(parent.getPlugins());
+
+ // We only use plugins now when they are defined on our level
+ // and not if it is in our parent. We inherit from our parent
+ // through the previous block.
+
+ if (properties.containsKey(PLUGIN)) {
+ String spe = getProperty(PLUGIN);
+ if (spe.equals(NONE))
+ return new LinkedHashSet<Object>();
+
+ String pluginPath = getProperty(PLUGINPATH);
+ loadPlugins(list, spe, pluginPath);
+ }
+
+ return this.plugins = list;
+ }
+
+ /**
+ * @param list
+ * @param spe
+ */
+ protected void loadPlugins(Set<Object> list, String spe, String pluginPath) {
+ Parameters plugins = new Parameters(spe);
+ CL loader = getLoader();
+
+ // First add the plugin-specific paths from their path: directives
+ for (Entry<String, Attrs> entry : plugins.entrySet()) {
+ String key = removeDuplicateMarker(entry.getKey());
+ String path = entry.getValue().get(PATH_DIRECTIVE);
+ if (path != null) {
+ String parts[] = path.split("\\s*,\\s*");
+ try {
+ for (String p : parts) {
+ File f = getFile(p).getAbsoluteFile();
+ loader.add(f.toURI().toURL());
+ }
+ } catch (Exception e) {
+ error("Problem adding path %s to loader for plugin %s. Exception: (%s)", path,
+ key, e);
+ }
+ }
+ }
+
+ // Next add -pluginpath entries
+ if (pluginPath != null && pluginPath.length() > 0) {
+ StringTokenizer tokenizer = new StringTokenizer(pluginPath, ",");
+ while (tokenizer.hasMoreTokens()) {
+ String path = tokenizer.nextToken().trim();
+ try {
+ File f = getFile(path).getAbsoluteFile();
+ loader.add(f.toURI().toURL());
+ } catch (Exception e) {
+ error("Problem adding path %s from global plugin path. Exception: %s", path, e);
+ }
+ }
+ }
+
+ // Load the plugins
+ for (Entry<String, Attrs> entry : plugins.entrySet()) {
+ String key = entry.getKey();
+
+ try {
+ trace("Using plugin %s", key);
+
+ // Plugins could use the same class with different
+ // parameters so we could have duplicate names Remove
+ // the ! added by the parser to make each name unique.
+ key = removeDuplicateMarker(key);
+
+ try {
+ Class<?> c = (Class<?>) loader.loadClass(key);
+ Object plugin = c.newInstance();
+ customize(plugin, entry.getValue());
+ list.add(plugin);
+ } catch (Throwable t) {
+ // We can defer the error if the plugin specifies
+ // a command name. In that case, we'll verify that
+ // a bnd file does not contain any references to a
+ // plugin
+ // command. The reason this feature was added was
+ // to compile plugin classes with the same build.
+ String commands = entry.getValue().get(COMMAND_DIRECTIVE);
+ if (commands == null)
+ error("Problem loading the plugin: %s exception: (%s)", key, t);
+ else {
+ Collection<String> cs = split(commands);
+ missingCommand.addAll(cs);
+ }
+ }
+ } catch (Throwable e) {
+ error("Problem loading the plugin: %s exception: (%s)", key, e);
+ }
+ }
+ }
+
+ protected void setTypeSpecificPlugins(Set<Object> list) {
+ list.add(executor);
+ list.add(random);
+ list.addAll(basicPlugins);
+ }
+
+ /**
+ * @param plugin
+ * @param entry
+ */
+ protected <T> T customize(T plugin, Attrs map) {
+ if (plugin instanceof Plugin) {
+ if (map != null)
+ ((Plugin) plugin).setProperties(map);
+
+ ((Plugin) plugin).setReporter(this);
+ }
+ if (plugin instanceof RegistryPlugin) {
+ ((RegistryPlugin) plugin).setRegistry(this);
+ }
+ return plugin;
+ }
+
+ public boolean isFailOk() {
+ String v = getProperty(Analyzer.FAIL_OK, null);
+ return v != null && v.equalsIgnoreCase("true");
+ }
+
+ public File getBase() {
+ return base;
+ }
+
+ public void setBase(File base) {
+ this.base = base;
+ }
+
+ public void clear() {
+ errors.clear();
+ warnings.clear();
+ }
+
+ public void trace(String msg, Object... parms) {
+ Processor p = current();
+ if (p.trace) {
+ System.err.printf("# " + msg + "%n", parms);
+ }
+ }
+
+ public <T> List<T> newList() {
+ return new ArrayList<T>();
+ }
+
+ public <T> Set<T> newSet() {
+ return new TreeSet<T>();
+ }
+
+ public static <K, V> Map<K, V> newMap() {
+ return new LinkedHashMap<K, V>();
+ }
+
+ public static <K, V> Map<K, V> newHashMap() {
+ return new LinkedHashMap<K, V>();
+ }
+
+ public <T> List<T> newList(Collection<T> t) {
+ return new ArrayList<T>(t);
+ }
+
+ public <T> Set<T> newSet(Collection<T> t) {
+ return new TreeSet<T>(t);
+ }
+
+ public <K, V> Map<K, V> newMap(Map<K, V> t) {
+ return new LinkedHashMap<K, V>(t);
+ }
+
+ public void close() {
+ for (Closeable c : toBeClosed) {
+ try {
+ c.close();
+ } catch (IOException e) {
+ // Who cares?
+ }
+ }
+ toBeClosed.clear();
+ }
+
+ public String _basedir(String args[]) {
+ if (base == null)
+ throw new IllegalArgumentException("No base dir set");
+
+ return base.getAbsolutePath();
+ }
+
+ /**
+ * Property handling ...
+ *
+ * @return
+ */
+
+ public Properties getProperties() {
+ if (fixup) {
+ fixup = false;
+ begin();
+ }
+
+ return properties;
+ }
+
+ public String getProperty(String key) {
+ return getProperty(key, null);
+ }
+
+ public void mergeProperties(File file, boolean override) {
+ if (file.isFile()) {
+ try {
+ Properties properties = loadProperties(file);
+ mergeProperties(properties, override);
+ } catch (Exception e) {
+ error("Error loading properties file: " + file);
+ }
+ } else {
+ if (!file.exists())
+ error("Properties file does not exist: " + file);
+ else
+ error("Properties file must a file, not a directory: " + file);
+ }
+ }
+
+ public void mergeProperties(Properties properties, boolean override) {
+ for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
+ String key = (String) e.nextElement();
+ String value = properties.getProperty(key);
+ if (override || !getProperties().containsKey(key))
+ setProperty(key, value);
+ }
+ }
+
+ public void setProperties(Properties properties) {
+ doIncludes(getBase(), properties);
+ this.properties.putAll(properties);
+ }
+
+ public void addProperties(File file) throws Exception {
+ addIncluded(file);
+ Properties p = loadProperties(file);
+ setProperties(p);
+ }
+
+ public void addProperties(Map<?, ?> properties) {
+ for (Entry<?, ?> entry : properties.entrySet()) {
+ setProperty(entry.getKey().toString(), entry.getValue() + "");
+ }
+ }
+
+ public synchronized void addIncluded(File file) {
+ if (included == null)
+ included = new ArrayList<File>();
+ included.add(file);
+ }
+
+ /**
+ * Inspect the properties and if you find -includes parse the line included
+ * manifest files or properties files. The files are relative from the given
+ * base, this is normally the base for the analyzer.
+ *
+ * @param ubase
+ * @param p
+ * @param done
+ * @throws IOException
+ * @throws IOException
+ */
+
+ private void doIncludes(File ubase, Properties p) {
+ String includes = p.getProperty(INCLUDE);
+ if (includes != null) {
+ includes = getReplacer().process(includes);
+ p.remove(INCLUDE);
+ Collection<String> clauses = new Parameters(includes).keySet();
+
+ for (String value : clauses) {
+ boolean fileMustExist = true;
+ boolean overwrite = true;
+ while (true) {
+ if (value.startsWith("-")) {
+ fileMustExist = false;
+ value = value.substring(1).trim();
+ } else if (value.startsWith("~")) {
+ // Overwrite properties!
+ overwrite = false;
+ value = value.substring(1).trim();
+ } else
+ break;
+ }
+ try {
+ File file = getFile(ubase, value).getAbsoluteFile();
+ if (!file.isFile() && fileMustExist) {
+ error("Included file " + file
+ + (file.exists() ? " does not exist" : " is directory"));
+ } else
+ doIncludeFile(file, overwrite, p);
+ } catch (Exception e) {
+ if (fileMustExist)
+ error("Error in processing included file: " + value, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param file
+ * @param parent
+ * @param done
+ * @param overwrite
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void doIncludeFile(File file, boolean overwrite, Properties target) throws Exception {
+ doIncludeFile(file, overwrite, target, null);
+ }
+
+ /**
+ * @param file
+ * @param parent
+ * @param done
+ * @param overwrite
+ * @param extensionName
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void doIncludeFile(File file, boolean overwrite, Properties target, String extensionName) throws Exception {
+ if (included != null && included.contains(file)) {
+ error("Cyclic or multiple include of " + file);
+ } else {
+ addIncluded(file);
+ updateModified(file.lastModified(), file.toString());
+ InputStream in = new FileInputStream(file);
+ try {
+ Properties sub;
+ if (file.getName().toLowerCase().endsWith(".mf")) {
+ sub = getManifestAsProperties(in);
+ } else
+ sub = loadProperties(in, file.getAbsolutePath());
+
+ doIncludes(file.getParentFile(), sub);
+ // make sure we do not override properties
+ for (Map.Entry<?, ?> entry : sub.entrySet()) {
+ String key = (String) entry.getKey();
+ String value = (String) entry.getValue();
+
+ if (overwrite || !target.containsKey(key)) {
+ target.setProperty(key, value);
+ } else if (extensionName != null) {
+ String extensionKey = extensionName + "." + key;
+ if (!target.containsKey(extensionKey))
+ target.setProperty(extensionKey, value);
+ }
+ }
+ } finally {
+ IO.close(in);
+ }
+ }
+ }
+
+ public void unsetProperty(String string) {
+ getProperties().remove(string);
+
+ }
+
+ public boolean refresh() {
+ plugins = null; // We always refresh our plugins
+
+ if (propertiesFile == null)
+ return false;
+
+ boolean changed = updateModified(propertiesFile.lastModified(), "properties file");
+ if (included != null) {
+ for (File file : included) {
+ if (changed)
+ break;
+
+ changed |= !file.exists()
+ || updateModified(file.lastModified(), "include file: " + file);
+ }
+ }
+
+ if (changed) {
+ forceRefresh();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ *
+ */
+ public void forceRefresh() {
+ included = null;
+ properties.clear();
+ setProperties(propertiesFile, base);
+ propertiesChanged();
+ }
+
+ public void propertiesChanged() {
+ }
+
+ /**
+ * Set the properties by file. Setting the properties this way will also set
+ * the base for this analyzer. After reading the properties, this will call
+ * setProperties(Properties) which will handle the includes.
+ *
+ * @param propertiesFile
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void setProperties(File propertiesFile) throws IOException {
+ propertiesFile = propertiesFile.getAbsoluteFile();
+ setProperties(propertiesFile, propertiesFile.getParentFile());
+ }
+
+ public void setProperties(File propertiesFile, File base) {
+ this.propertiesFile = propertiesFile.getAbsoluteFile();
+ setBase(base);
+ try {
+ if (propertiesFile.isFile()) {
+ // System.err.println("Loading properties " + propertiesFile);
+ long modified = propertiesFile.lastModified();
+ if (modified > System.currentTimeMillis() + 100) {
+ System.err.println("Huh? This is in the future " + propertiesFile);
+ this.modified = System.currentTimeMillis();
+ } else
+ this.modified = modified;
+
+ included = null;
+ Properties p = loadProperties(propertiesFile);
+ setProperties(p);
+ } else {
+ if (fileMustExist) {
+ error("No such properties file: " + propertiesFile);
+ }
+ }
+ } catch (IOException e) {
+ error("Could not load properties " + propertiesFile);
+ }
+ }
+
+ protected void begin() {
+ if (isTrue(getProperty(PEDANTIC)))
+ setPedantic(true);
+ }
+
+ public static boolean isTrue(String value) {
+ if (value == null)
+ return false;
+
+ return !"false".equalsIgnoreCase(value);
+ }
+
+ /**
+ * Get a property without preprocessing it with a proper default
+ *
+ * @param headerName
+ * @param deflt
+ * @return
+ */
+
+ public String getUnprocessedProperty(String key, String deflt) {
+ return getProperties().getProperty(key, deflt);
+ }
+
+ /**
+ * Get a property with preprocessing it with a proper default
+ *
+ * @param headerName
+ * @param deflt
+ * @return
+ */
+ public String getProperty(String key, String deflt) {
+ String value = null;
+
+ Instruction ins = new Instruction(key);
+ if (!ins.isLiteral()) {
+ // Handle a wildcard key, make sure they're sorted
+ // for consistency
+ SortedList<String> sortedList = SortedList.fromIterator(iterator());
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (String k : sortedList) {
+ if (ins.matches(k)) {
+ String v = getProperty(k, null);
+ if (v != null) {
+ sb.append(del);
+ del = ",";
+ sb.append(v);
+ }
+ }
+ }
+ if (sb.length() == 0)
+ return deflt;
+
+ return sb.toString();
+ }
+
+ Processor source = this;
+
+ if (filter != null && filter.contains(key)) {
+ value = (String) getProperties().get(key);
+ } else {
+ while (source != null) {
+ value = (String) source.getProperties().get(key);
+ if (value != null)
+ break;
+
+ source = source.getParent();
+ }
+ }
+
+ if (value != null)
+ return getReplacer().process(value, source);
+ else if (deflt != null)
+ return getReplacer().process(deflt, this);
+ else
+ return null;
+ }
+
+ /**
+ * Helper to load a properties file from disk.
+ *
+ * @param file
+ * @return
+ * @throws IOException
+ */
+ public Properties loadProperties(File file) throws IOException {
+ updateModified(file.lastModified(), "Properties file: " + file);
+ InputStream in = new FileInputStream(file);
+ try {
+ Properties p = loadProperties(in, file.getAbsolutePath());
+ return p;
+ } finally {
+ in.close();
+ }
+ }
+
+ Properties loadProperties(InputStream in, String name) throws IOException {
+ int n = name.lastIndexOf('/');
+ if (n > 0)
+ name = name.substring(0, n);
+ if (name.length() == 0)
+ name = ".";
+
+ try {
+ Properties p = new Properties();
+ p.load(in);
+ return replaceAll(p, "\\$\\{\\.\\}", name);
+ } catch (Exception e) {
+ error("Error during loading properties file: " + name + ", error:" + e);
+ return new Properties();
+ }
+ }
+
+ /**
+ * Replace a string in all the values of the map. This can be used to
+ * preassign variables that change. I.e. the base directory ${.} for a
+ * loaded properties
+ */
+
+ public static Properties replaceAll(Properties p, String pattern, String replacement) {
+ Properties result = new Properties();
+ for (Iterator<Map.Entry<Object, Object>> i = p.entrySet().iterator(); i.hasNext();) {
+ Map.Entry<Object, Object> entry = i.next();
+ String key = (String) entry.getKey();
+ String value = (String) entry.getValue();
+ value = value.replaceAll(pattern, replacement);
+ result.put(key, value);
+ }
+ return result;
+ }
+
+ /**
+ * Print a standard Map based OSGi header.
+ *
+ * @param exports
+ * map { name => Map { attribute|directive => value } }
+ * @return the clauses
+ * @throws IOException
+ */
+ public static String printClauses(Map<?, ? extends Map<?, ?>> exports) throws IOException {
+ return printClauses(exports, false);
+ }
+
+ public static String printClauses(Map<?, ? extends Map<?, ?>> exports,
+ boolean checkMultipleVersions) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (Entry<?, ? extends Map<?, ?>> entry : exports.entrySet()) {
+ String name = entry.getKey().toString();
+ Map<?, ?> clause = entry.getValue();
+
+ // We allow names to be duplicated in the input
+ // by ending them with '~'. This is necessary to use
+ // the package names as keys. However, we remove these
+ // suffixes in the output so that you can set multiple
+ // exports with different attributes.
+ String outname = removeDuplicateMarker(name);
+ sb.append(del);
+ sb.append(outname);
+ printClause(clause, sb);
+ del = ",";
+ }
+ return sb.toString();
+ }
+
+ public static void printClause(Map<?, ?> map, StringBuilder sb) throws IOException {
+
+ for (Entry<?, ?> entry : map.entrySet()) {
+ Object key = entry.getKey();
+ // Skip directives we do not recognize
+ if (key.equals(NO_IMPORT_DIRECTIVE) || key.equals(PROVIDE_DIRECTIVE)
+ || key.equals(SPLIT_PACKAGE_DIRECTIVE) || key.equals(FROM_DIRECTIVE))
+ continue;
+
+ String value = ((String) entry.getValue()).trim();
+ sb.append(";");
+ sb.append(key);
+ sb.append("=");
+
+ quote(sb, value);
+ }
+ }
+
+ /**
+ * @param sb
+ * @param value
+ * @return
+ * @throws IOException
+ */
+ public static boolean quote(Appendable sb, String value) throws IOException {
+ boolean clean = (value.length() >= 2 && value.charAt(0) == '"' && value.charAt(value
+ .length() - 1) == '"') || Verifier.TOKEN.matcher(value).matches();
+ if (!clean)
+ sb.append("\"");
+ sb.append(value);
+ if (!clean)
+ sb.append("\"");
+ return clean;
+ }
+
+ public Macro getReplacer() {
+ if (replacer == null)
+ return replacer = new Macro(this, getMacroDomains());
+ else
+ return replacer;
+ }
+
+ /**
+ * This should be overridden by subclasses to add extra macro command
+ * domains on the search list.
+ *
+ * @return
+ */
+ protected Object[] getMacroDomains() {
+ return new Object[] {};
+ }
+
+ /**
+ * Return the properties but expand all macros. This always returns a new
+ * Properties object that can be used in any way.
+ *
+ * @return
+ */
+ public Properties getFlattenedProperties() {
+ return getReplacer().getFlattenedProperties();
+
+ }
+
+ /**
+ * Return all inherited property keys
+ *
+ * @return
+ */
+ public Set<String> getPropertyKeys(boolean inherit) {
+ Set<String> result;
+ if (parent == null || !inherit) {
+ result = Create.set();
+ } else
+ result = parent.getPropertyKeys(inherit);
+ for (Object o : properties.keySet())
+ result.add(o.toString());
+
+ return result;
+ }
+
+ public boolean updateModified(long time, String reason) {
+ if (time > lastModified) {
+ lastModified = time;
+ return true;
+ }
+ return false;
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ /**
+ * Add or override a new property.
+ *
+ * @param key
+ * @param value
+ */
+ public void setProperty(String key, String value) {
+ checkheader: for (int i = 0; i < headers.length; i++) {
+ if (headers[i].equalsIgnoreCase(value)) {
+ value = headers[i];
+ break checkheader;
+ }
+ }
+ getProperties().put(key, value);
+ }
+
+ /**
+ * Read a manifest but return a properties object.
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ */
+ public static Properties getManifestAsProperties(InputStream in) throws IOException {
+ Properties p = new Properties();
+ Manifest manifest = new Manifest(in);
+ for (Iterator<Object> it = manifest.getMainAttributes().keySet().iterator(); it.hasNext();) {
+ Attributes.Name key = (Attributes.Name) it.next();
+ String value = manifest.getMainAttributes().getValue(key);
+ p.put(key.toString(), value);
+ }
+ return p;
+ }
+
+ public File getPropertiesFile() {
+ return propertiesFile;
+ }
+
+ public void setFileMustExist(boolean mustexist) {
+ fileMustExist = mustexist;
+ }
+
+ static public String read(InputStream in) throws Exception {
+ InputStreamReader ir = new InputStreamReader(in, "UTF8");
+ StringBuilder sb = new StringBuilder();
+
+ try {
+ char chars[] = new char[1000];
+ int size = ir.read(chars);
+ while (size > 0) {
+ sb.append(chars, 0, size);
+ size = ir.read(chars);
+ }
+ } finally {
+ ir.close();
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Join a list.
+ *
+ * @param args
+ * @return
+ */
+ public static String join(Collection<?> list, String delimeter) {
+ return join(delimeter, list);
+ }
+
+ public static String join(String delimeter, Collection<?>... list) {
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ if (list != null) {
+ for (Collection<?> l : list) {
+ for (Object item : l) {
+ sb.append(del);
+ sb.append(item);
+ del = delimeter;
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ public static String join(Object[] list, String delimeter) {
+ if (list == null)
+ return "";
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (Object item : list) {
+ sb.append(del);
+ sb.append(item);
+ del = delimeter;
+ }
+ return sb.toString();
+ }
+
+ public static String join(Collection<?>... list) {
+ return join(",", list);
+ }
+
+ public static <T> String join(T list[]) {
+ return join(list, ",");
+ }
+
+ public static void split(String s, Collection<String> set) {
+
+ String elements[] = s.trim().split(LIST_SPLITTER);
+ for (String element : elements) {
+ if (element.length() > 0)
+ set.add(element);
+ }
+ }
+
+ public static Collection<String> split(String s) {
+ return split(s, LIST_SPLITTER);
+ }
+
+ public static Collection<String> split(String s, String splitter) {
+ if (s != null)
+ s = s.trim();
+ if (s == null || s.trim().length() == 0)
+ return Collections.emptyList();
+
+ return Arrays.asList(s.split(splitter));
+ }
+
+ public static String merge(String... strings) {
+ ArrayList<String> result = new ArrayList<String>();
+ for (String s : strings) {
+ if (s != null)
+ split(s, result);
+ }
+ return join(result);
+ }
+
+ public boolean isExceptions() {
+ return exceptions;
+ }
+
+ public void setExceptions(boolean exceptions) {
+ this.exceptions = exceptions;
+ }
+
+ /**
+ * Make the file short if it is inside our base directory, otherwise long.
+ *
+ * @param f
+ * @return
+ */
+ public String normalize(String f) {
+ if (f.startsWith(base.getAbsolutePath() + "/"))
+ return f.substring(base.getAbsolutePath().length() + 1);
+ else
+ return f;
+ }
+
+ public String normalize(File f) {
+ return normalize(f.getAbsolutePath());
+ }
+
+ public static String removeDuplicateMarker(String key) {
+ int i = key.length() - 1;
+ while (i >= 0 && key.charAt(i) == DUPLICATE_MARKER)
+ --i;
+
+ return key.substring(0, i + 1);
+ }
+
+ public static boolean isDuplicate(String name) {
+ return name.length() > 0 && name.charAt(name.length() - 1) == DUPLICATE_MARKER;
+ }
+
+ public void setTrace(boolean x) {
+ trace = x;
+ }
+
+ static class CL extends URLClassLoader {
+
+ CL() {
+ super(new URL[0], Processor.class.getClassLoader());
+ }
+
+ void add(URL url) {
+ URL urls[] = getURLs();
+ for (URL u : urls) {
+ if (u.equals(url))
+ return;
+ }
+ super.addURL(url);
+ }
+
+ public Class<?> loadClass(String name) throws NoClassDefFoundError {
+ try {
+ Class<?> c = super.loadClass(name);
+ return c;
+ } catch (Throwable t) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(name);
+ sb.append(" not found, parent: ");
+ sb.append(getParent());
+ sb.append(" urls:");
+ sb.append(Arrays.toString(getURLs()));
+ sb.append(" exception:");
+ sb.append(t);
+ throw new NoClassDefFoundError(sb.toString());
+ }
+ }
+ }
+
+ private CL getLoader() {
+ if (pluginLoader == null) {
+ pluginLoader = new CL();
+ }
+ return pluginLoader;
+ }
+
+ /*
+ * Check if this is a valid project.
+ */
+ public boolean exists() {
+ return base != null && base.isDirectory() && propertiesFile != null
+ && propertiesFile.isFile();
+ }
+
+ public boolean isOk() {
+ return isFailOk() || (getErrors().size() == 0);
+ }
+
+ public boolean check(String... pattern) throws IOException {
+ 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())
+ System.err
+ .println("Missed the following patterns in the warnings or errors: " + missed);
+
+ report(System.err);
+ return false;
+ }
+
+ protected void report(Appendable out) throws IOException {
+ if (errors.size() > 0) {
+ out.append("-----------------\nErrors\n");
+ for (int i = 0; i < errors.size(); i++) {
+ out.append(String.format("%03d: %s\n", i, errors.get(i)));
+ }
+ }
+ if (warnings.size() > 0) {
+ out.append(String.format("-----------------\nWarnings\n"));
+ for (int i = 0; i < warnings.size(); i++) {
+ out.append(String.format("%03d: %s\n", i, warnings.get(i)));
+ }
+ }
+ }
+
+ public boolean isPerfect() {
+ return getErrors().size() == 0 && getWarnings().size() == 0;
+ }
+
+ public void setForceLocal(Collection<String> local) {
+ filter = local;
+ }
+
+ /**
+ * Answer if the name is a missing plugin's command name. If a bnd file
+ * contains the command name of a plugin, and that plugin is not available,
+ * then an error is reported during manifest calculation. This allows the
+ * plugin to fail to load when it is not needed.
+ *
+ * We first get the plugins to ensure it is properly initialized.
+ *
+ * @param name
+ * @return
+ */
+ public boolean isMissingPlugin(String name) {
+ getPlugins();
+ return missingCommand != null && missingCommand.contains(name);
+ }
+
+ /**
+ * Append two strings to for a path in a ZIP or JAR file. It is guaranteed
+ * to return a string that does not start, nor ends with a '/', while it is
+ * properly separated with slashes. Double slashes are properly removed.
+ *
+ * <pre>
+ * "/" + "abc/def/" becomes "abc/def"
+ *
+ * @param prefix
+ * @param suffix
+ * @return
+ *
+ */
+ public static String appendPath(String... parts) {
+ StringBuilder sb = new StringBuilder();
+ boolean lastSlash = true;
+ for (String part : parts) {
+ for (int i = 0; i < part.length(); i++) {
+ char c = part.charAt(i);
+ if (c == '/') {
+ if (!lastSlash)
+ sb.append('/');
+ lastSlash = true;
+ } else {
+ sb.append(c);
+ lastSlash = false;
+ }
+ }
+
+ if (!lastSlash && sb.length() > 0) {
+ sb.append('/');
+ lastSlash = true;
+ }
+ }
+ if (lastSlash && sb.length() > 0)
+ sb.deleteCharAt(sb.length() - 1);
+
+ return sb.toString();
+ }
+
+ /**
+ * Parse the a=b strings and return a map of them.
+ *
+ * @param attrs
+ * @param clazz
+ * @return
+ */
+ public static Attrs doAttrbutes(Object[] attrs, Clazz clazz, Macro macro) {
+ Attrs map = new Attrs();
+
+ if (attrs == null || attrs.length == 0)
+ return map;
+
+ for (Object a : attrs) {
+ String attr = (String) a;
+ int n = attr.indexOf("=");
+ if (n > 0) {
+ map.put(attr.substring(0, n), macro.process(attr.substring(n + 1)));
+ } else
+ throw new IllegalArgumentException(formatArrays(
+ "Invalid attribute on package-info.java in %s , %s. Must be <key>=<name> ",
+ clazz, attr));
+ }
+ return map;
+ }
+
+ /**
+ * This method is the same as String.format but it makes sure that any
+ * arrays are transformed to strings.
+ *
+ * @param string
+ * @param parms
+ * @return
+ */
+ public static String formatArrays(String string, Object... parms) {
+ Object[] parms2 = parms;
+ Object[] output = new Object[parms.length];
+ for (int i = 0; i < parms.length; i++) {
+ output[i] = makePrintable(parms[i]);
+ }
+ return String.format(string, parms2);
+ }
+
+ /**
+ * Check if the object is an array and turn it into a string if it is,
+ * otherwise unchanged.
+ *
+ * @param object
+ * the object to make printable
+ * @return a string if it was an array or the original object
+ */
+ public static Object makePrintable(Object object) {
+ if (object == null)
+ return object;
+
+ if (object.getClass().isArray()) {
+ Object[] array = (Object[]) object;
+ Object[] output = new Object[array.length];
+ for (int i = 0; i < array.length; i++) {
+ output[i] = makePrintable(array[i]);
+ }
+ return Arrays.toString(output);
+ }
+ return object;
+ }
+
+ public static String append(String... strings) {
+ List<String> result = Create.list();
+ for (String s : strings) {
+ result.addAll(split(s));
+ }
+ return join(result);
+ }
+
+ public synchronized Class<?> getClass(String type, File jar) throws Exception {
+ CL cl = getLoader();
+ cl.add(jar.toURI().toURL());
+ return cl.loadClass(type);
+ }
+
+ public boolean isTrace() {
+ return current().trace;
+ }
+
+ public static long getDuration(String tm, long dflt) {
+ if (tm == null)
+ return dflt;
+
+ tm = tm.toUpperCase();
+ TimeUnit unit = TimeUnit.MILLISECONDS;
+ Matcher m = Pattern
+ .compile(
+ "\\s*(\\d+)\\s*(NANOSECONDS|MICROSECONDS|MILLISECONDS|SECONDS|MINUTES|HOURS|DAYS)?")
+ .matcher(tm);
+ if (m.matches()) {
+ long duration = Long.parseLong(tm);
+ String u = m.group(2);
+ if (u != null)
+ unit = TimeUnit.valueOf(u);
+ duration = TimeUnit.MILLISECONDS.convert(duration, unit);
+ return duration;
+ }
+ return dflt;
+ }
+
+ /**
+ * Generate a random string, which is guaranteed to be a valid Java
+ * identifier (first character is an ASCII letter, subsequent characters are
+ * ASCII letters or numbers). Takes an optional parameter for the length of
+ * string to generate; default is 8 characters.
+ */
+ public String _random(String[] args) {
+ int numchars = 8;
+ if (args.length > 1) {
+ try {
+ numchars = Integer.parseInt(args[1]);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "Invalid character count parameter in ${random} macro.");
+ }
+ }
+
+ synchronized (Processor.class) {
+ if (random == null)
+ random = new Random();
+ }
+
+ char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+ char[] alphanums = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ .toCharArray();
+
+ char[] array = new char[numchars];
+ for (int i = 0; i < numchars; i++) {
+ char c;
+ if (i == 0)
+ c = letters[random.nextInt(letters.length)];
+ else
+ c = alphanums[random.nextInt(alphanums.length)];
+ array[i] = c;
+ }
+
+ return new String(array);
+ }
+
+ /**
+ * Set the current command thread. This must be balanced with the
+ * {@link #end(Processor)} method. The method returns the previous command
+ * owner or null.
+ *
+ * The command owner will receive all warnings and error reports.
+ */
+
+ protected Processor beginHandleErrors(String message) {
+ trace("begin %s", message);
+ Processor previous = current.get();
+ current.set(this);
+ return previous;
+ }
+
+ /**
+ * End a command. Will restore the previous command owner.
+ *
+ * @param previous
+ */
+ protected void endHandleErrors(Processor previous) {
+ trace("end");
+ current.set(previous);
+ }
+
+ public static Executor getExecutor() {
+ return executor;
+ }
+
+ /**
+ * These plugins are added to the total list of plugins. The separation is
+ * necessary because the list of plugins is refreshed now and then so we
+ * need to be able to add them at any moment in time.
+ *
+ * @param plugin
+ */
+ public synchronized void addBasicPlugin(Object plugin) {
+ basicPlugins.add(plugin);
+ if (plugins != null)
+ plugins.add(plugin);
+ }
+
+ public synchronized void removeBasicPlugin(Object plugin) {
+ basicPlugins.remove(plugin);
+ if (plugins != null)
+ plugins.remove(plugin);
+ }
+
+ public List<File> getIncluded() {
+ return included;
+ }
+
+ /**
+ * Overrides for the Domain class
+ */
+ @Override public String get(String key) {
+ return getProperty(key);
+ }
+
+ @Override public String get(String key, String deflt) {
+ return getProperty(key, deflt);
+ }
+
+ @Override public void set(String key, String value) {
+ getProperties().setProperty(key, value);
+ }
+
+ @Override public Iterator<String> iterator() {
+ Set<String> keys = keySet();
+ final Iterator<String> it = keys.iterator();
+
+ return new Iterator<String>() {
+ String current;
+
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public String next() {
+ return current = it.next().toString();
+ }
+
+ public void remove() {
+ getProperties().remove(current);
+ }
+ };
+ }
+
+ public Set<String> keySet() {
+ Set<String> set;
+ if (parent == null)
+ set = Create.set();
+ else
+ set = parent.keySet();
+
+ for (Object o : properties.keySet())
+ set.add(o.toString());
+
+ return set;
+ }
+
+ /**
+ * Printout of the status of this processor for toString()
+ */
+
+ public String toString() {
+ try {
+ StringBuilder sb = new StringBuilder();
+ report(sb);
+ return sb.toString();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Utiltity to replace an extension
+ *
+ * @param s
+ * @param extension
+ * @param newExtension
+ * @return
+ */
+ public String replaceExtension(String s, String extension, String newExtension) {
+ if (s.endsWith(extension))
+ s = s.substring(0, s.length() - extension.length());
+
+ return s + newExtension;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Resource.java b/bundleplugin/src/main/java/aQute/lib/osgi/Resource.java
new file mode 100755
index 0000000..f7df287
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Resource.java
@@ -0,0 +1,12 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public interface Resource {
+ InputStream openInputStream() throws Exception ;
+ void write(OutputStream out) throws Exception;
+ long lastModified();
+ void setExtra(String extra);
+ String getExtra();
+ long size() throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/TagResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/TagResource.java
new file mode 100644
index 0000000..0318427
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/TagResource.java
@@ -0,0 +1,30 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+import aQute.lib.tag.*;
+
+public class TagResource extends WriteResource {
+ final Tag tag;
+
+ public TagResource(Tag tag) {
+ this.tag = tag;
+ }
+
+
+ public void write(OutputStream out) throws UnsupportedEncodingException {
+ OutputStreamWriter ow = new OutputStreamWriter(out, "UTF-8");
+ PrintWriter pw = new PrintWriter(ow);
+ pw.println("<?xml version='1.1'?>");
+ try {
+ tag.print(0, pw);
+ } finally {
+ pw.flush();
+ }
+ }
+
+ public long lastModified() {
+ return 0;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/URLResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/URLResource.java
new file mode 100755
index 0000000..6e96f23
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/URLResource.java
@@ -0,0 +1,82 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.net.*;
+
+import aQute.lib.io.*;
+
+public class URLResource implements Resource {
+ URL url;
+ String extra;
+ long size = -1;
+
+ public URLResource(URL url) {
+ this.url = url;
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return url.openStream();
+ }
+
+ public String toString() {
+ return ":" + url.getPath() + ":";
+ }
+
+ public void write(OutputStream out) throws Exception {
+ IO.copy(this.openInputStream(), out);
+ }
+
+ public long lastModified() {
+ return -1;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public long size() throws Exception {
+ if (size >= 0)
+ return size;
+
+ try {
+ if (url.getProtocol().equals("file:")) {
+ File file = new File(url.getPath());
+ if ( file.isFile())
+ return size = file.length();
+ } else {
+ URLConnection con = url.openConnection();
+ if (con instanceof HttpURLConnection) {
+ HttpURLConnection http = (HttpURLConnection) con;
+ http.setRequestMethod("HEAD");
+ http.connect();
+ String l = http.getHeaderField("Content-Length");
+ if (l != null) {
+ return size = Long.parseLong(l);
+ }
+ }
+ }
+ } catch (Exception e) {
+ // Forget this exception, we do it the hard way
+ }
+ InputStream in = openInputStream();
+ DataInputStream din = null;
+ try {
+ din = new DataInputStream(in);
+ long result = din.skipBytes(Integer.MAX_VALUE);
+ while( in.read() >= 0) {
+ result += din.skipBytes(Integer.MAX_VALUE);
+ }
+ size = result;
+ } finally {
+ if (din != null) {
+ din.close();
+ }
+ }
+ return size;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java b/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java
new file mode 100755
index 0000000..89bd7f0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java
@@ -0,0 +1,938 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.base64.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.cryptography.*;
+import aQute.libg.header.*;
+import aQute.libg.qtokens.*;
+
+public class Verifier extends Processor {
+
+ private final Jar dot;
+ private final Manifest manifest;
+ private final Domain main;
+
+ private boolean r3;
+ private boolean usesRequire;
+
+ final static Pattern EENAME = Pattern.compile("CDC-1\\.0/Foundation-1\\.0"
+ + "|CDC-1\\.1/Foundation-1\\.1"
+ + "|OSGi/Minimum-1\\.[1-9]" + "|JRE-1\\.1"
+ + "|J2SE-1\\.2" + "|J2SE-1\\.3" + "|J2SE-1\\.4"
+ + "|J2SE-1\\.5" + "|JavaSE-1\\.6" + "|JavaSE-1\\.7"
+ + "|PersonalJava-1\\.1" + "|PersonalJava-1\\.2"
+ + "|CDC-1\\.0/PersonalBasis-1\\.0"
+ + "|CDC-1\\.0/PersonalJava-1\\.0");
+
+ final static int V1_1 = 45;
+ final static int V1_2 = 46;
+ final static int V1_3 = 47;
+ final static int V1_4 = 48;
+ final static int V1_5 = 49;
+ final static int V1_6 = 50;
+ final static int V1_7 = 51;
+ final static int V1_8 = 52;
+
+ static class EE {
+ String name;
+ int target;
+
+ EE(String name, int source, int target) {
+ this.name = name;
+ this.target = target;
+ }
+
+ public String toString() {
+ return name + "(" + target + ")";
+ }
+ }
+
+ final static EE[] ees = {
+ new EE("CDC-1.0/Foundation-1.0", V1_3, V1_1),
+ new EE("CDC-1.1/Foundation-1.1", V1_3, V1_2),
+ new EE("OSGi/Minimum-1.0", V1_3, V1_1),
+ new EE("OSGi/Minimum-1.1", V1_3, V1_2),
+ new EE("JRE-1.1", V1_1, V1_1), //
+ new EE("J2SE-1.2", V1_2, V1_1), //
+ new EE("J2SE-1.3", V1_3, V1_1), //
+ new EE("J2SE-1.4", V1_3, V1_2), //
+ new EE("J2SE-1.5", V1_5, V1_5), //
+ new EE("JavaSE-1.6", V1_6, V1_6), //
+ new EE("PersonalJava-1.1", V1_1, V1_1), //
+ new EE("JavaSE-1.7", V1_7, V1_7), //
+ new EE("PersonalJava-1.1", V1_1, V1_1), //
+ new EE("PersonalJava-1.2", V1_1, V1_1),
+ new EE("CDC-1.0/PersonalBasis-1.0", V1_3, V1_1),
+ new EE("CDC-1.0/PersonalJava-1.0", V1_3, V1_1),
+ new EE("CDC-1.1/PersonalBasis-1.1", V1_3, V1_2),
+ new EE("CDC-1.1/PersonalJava-1.1", V1_3, V1_2) };
+
+ final static Pattern CARDINALITY_PATTERN = Pattern
+ .compile("single|multiple");
+ final static Pattern RESOLUTION_PATTERN = Pattern
+ .compile("optional|mandatory");
+ final static Pattern BUNDLEMANIFESTVERSION = Pattern.compile("2");
+ public final static String SYMBOLICNAME_STRING = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
+ public final static Pattern SYMBOLICNAME = Pattern
+ .compile(SYMBOLICNAME_STRING);
+
+ public final static String VERSION_STRING = "[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9A-Za-z_-]+)?)?)?";
+ public final static Pattern VERSION = Pattern.compile(VERSION_STRING);
+ final static Pattern FILTEROP = Pattern.compile("=|<=|>=|~=");
+ public final static Pattern VERSIONRANGE = Pattern.compile("((\\(|\\[)"
+
+ + VERSION_STRING + ","
+ + VERSION_STRING
+ + "(\\]|\\)))|"
+ + VERSION_STRING);
+ final static Pattern FILE = Pattern
+ .compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
+ final static Pattern WILDCARDPACKAGE = Pattern
+ .compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
+ public final static Pattern ISO639 = Pattern.compile("[A-Z][A-Z]");
+ public final static Pattern HEADER_PATTERN = Pattern
+ .compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
+ public final static Pattern TOKEN = Pattern.compile("[-a-zA-Z0-9_]+");
+
+ public final static Pattern NUMBERPATTERN = Pattern.compile("\\d+");
+ public final static Pattern PACKAGEPATTERN = Pattern
+ .compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*");
+ public final static Pattern PATHPATTERN = Pattern.compile(".*");
+ public final static Pattern FQNPATTERN = Pattern.compile(".*");
+ public final static Pattern URLPATTERN = Pattern.compile(".*");
+ public final static Pattern ANYPATTERN = Pattern.compile(".*");
+ public final static Pattern FILTERPATTERN = Pattern.compile(".*");
+ public final static Pattern TRUEORFALSEPATTERN = Pattern
+ .compile("true|false|TRUE|FALSE");
+ public static final Pattern WILDCARDNAMEPATTERN = Pattern.compile(".*");
+ public static final Pattern BUNDLE_ACTIVATIONPOLICYPATTERN = Pattern.compile("lazy");
+
+ public final static String EES[] = { "CDC-1.0/Foundation-1.0",
+ "CDC-1.1/Foundation-1.1", "OSGi/Minimum-1.0", "OSGi/Minimum-1.1", "OSGi/Minimum-1.2",
+ "JRE-1.1", "J2SE-1.2", "J2SE-1.3", "J2SE-1.4", "J2SE-1.5", "JavaSE-1.6", "JavaSE-1.7",
+ "PersonalJava-1.1", "PersonalJava-1.2", "CDC-1.0/PersonalBasis-1.0",
+ "CDC-1.0/PersonalJava-1.0" };
+
+ public final static String OSNAMES[] = { "AIX", // IBM
+ "DigitalUnix", // Compaq
+ "Embos", // Segger Embedded Software Solutions
+ "Epoc32", // SymbianOS Symbian OS
+ "FreeBSD", // Free BSD
+ "HPUX", // hp-ux Hewlett Packard
+ "IRIX", // Silicon Graphics
+ "Linux", // Open source
+ "MacOS", // Apple
+ "NetBSD", // Open source
+ "Netware", // Novell
+ "OpenBSD", // Open source
+ "OS2", // OS/2 IBM
+ "QNX", // procnto QNX
+ "Solaris", // Sun (almost an alias of SunOS)
+ "SunOS", // Sun Microsystems
+ "VxWorks", // WindRiver Systems
+ "Windows95", "Win32", "Windows98", "WindowsNT", "WindowsCE", "Windows2000", // Win2000
+ "Windows2003", // Win2003
+ "WindowsXP", "WindowsVista", };
+
+ public final static String PROCESSORNAMES[] = { //
+ //
+ "68k", // Motorola 68000
+ "ARM_LE", // Intel Strong ARM. Deprecated because it does not
+ // specify the endianness. See the following two rows.
+ "arm_le", // Intel Strong ARM Little Endian mode
+ "arm_be", // Intel String ARM Big Endian mode
+ "Alpha", //
+ "ia64n",// Hewlett Packard 32 bit
+ "ia64w",// Hewlett Packard 64 bit mode
+ "Ignite", // psc1k PTSC
+ "Mips", // SGI
+ "PArisc", // Hewlett Packard
+ "PowerPC", // power ppc Motorola/IBM Power PC
+ "Sh4", // Hitachi
+ "Sparc", // SUN
+ "Sparcv9", // SUN
+ "S390", // IBM Mainframe 31 bit
+ "S390x", // IBM Mainframe 64-bit
+ "V850E", // NEC V850E
+ "x86", // pentium i386
+ "i486", // i586 i686 Intel& AMD 32 bit
+ "x86-64", };
+
+ final Analyzer analyzer;
+ private Instructions dynamicImports;
+
+ public Verifier(Jar jar) throws Exception {
+ this.analyzer = new Analyzer(this);
+ this.analyzer.use(this);
+ addClose(analyzer);
+ this.analyzer.setJar(jar);
+ this.manifest = this.analyzer.calcManifest();
+ this.main = Domain.domain(manifest);
+ this.dot = jar;
+ getInfo(analyzer);
+ }
+
+ public Verifier(Analyzer analyzer) throws Exception {
+ this.analyzer = analyzer;
+ this.dot = analyzer.getJar();
+ this.manifest = dot.getManifest();
+ this.main = Domain.domain(manifest);
+ }
+
+ private void verifyHeaders() {
+ for (String h : main) {
+ if (!HEADER_PATTERN.matcher(h).matches())
+ error("Invalid Manifest header: " + h + ", pattern=" + HEADER_PATTERN);
+ }
+ }
+
+ /*
+ * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
+ * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
+ * optional ::= ’*’
+ */
+ public void verifyNative() {
+ String nc = get("Bundle-NativeCode");
+ doNative(nc);
+ }
+
+ public void doNative(String nc) {
+ if (nc != null) {
+ QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
+ char del;
+ do {
+ do {
+ String name = qt.nextToken();
+ if (name == null) {
+ error("Can not parse name from bundle native code header: " + nc);
+ return;
+ }
+ del = qt.getSeparator();
+ if (del == ';') {
+ if (dot != null && !dot.exists(name)) {
+ error("Native library not found in JAR: " + name);
+ }
+ } else {
+ String value = null;
+ if (del == '=')
+ value = qt.nextToken();
+
+ String key = name.toLowerCase();
+ if (key.equals("osname")) {
+ // ...
+ } else if (key.equals("osversion")) {
+ // verify version range
+ verify(value, VERSIONRANGE);
+ } else if (key.equals("language")) {
+ verify(value, ISO639);
+ } else if (key.equals("processor")) {
+ // verify(value, PROCESSORS);
+ } else if (key.equals("selection-filter")) {
+ // verify syntax filter
+ verifyFilter(value);
+ } else if (name.equals("*") && value == null) {
+ // Wildcard must be at end.
+ if (qt.nextToken() != null)
+ error("Bundle-Native code header may only END in wildcard: nc");
+ } else {
+ warning("Unknown attribute in native code: " + name + "=" + value);
+ }
+ del = qt.getSeparator();
+ }
+ } while (del == ';');
+ } while (del == ',');
+ }
+ }
+
+ public boolean verifyFilter(String value) {
+ String s = validateFilter(value);
+ if (s == null)
+ return true;
+
+ error(s);
+ return false;
+ }
+
+ public static String validateFilter(String value) {
+ try {
+ verifyFilter(value, 0);
+ return null;
+ } catch (Exception e) {
+ return "Not a valid filter: " + value + e.getMessage();
+ }
+ }
+
+ private void verifyActivator() throws Exception {
+ String bactivator = main.get("Bundle-Activator");
+ if (bactivator != null) {
+ TypeRef ref = analyzer.getTypeRefFromFQN(bactivator);
+ if (analyzer.getClassspace().containsKey(ref))
+ return;
+
+ PackageRef packageRef = ref.getPackageRef();
+ if (packageRef.isDefaultPackage())
+ error("The Bundle Activator is not in the bundle and it is in the default package ");
+ else if (!analyzer.isImported(packageRef)) {
+ error("Bundle-Activator not found on the bundle class path nor in imports: "
+ + bactivator);
+ }
+ }
+ }
+
+ private void verifyComponent() {
+ String serviceComponent = main.get("Service-Component");
+ if (serviceComponent != null) {
+ Parameters map = parseHeader(serviceComponent);
+ for (String component : map.keySet()) {
+ if (component.indexOf("*") < 0 && !dot.exists(component)) {
+ error("Service-Component entry can not be located in JAR: " + component);
+ } else {
+ // validate component ...
+ }
+ }
+ }
+ }
+
+ /**
+ * Check for unresolved imports. These are referrals that are not imported
+ * by the manifest and that are not part of our bundle class path. The are
+ * calculated by removing all the imported packages and contained from the
+ * referred packages.
+ */
+ private void verifyUnresolvedReferences() {
+ Set<PackageRef> unresolvedReferences = new TreeSet<PackageRef>(analyzer.getReferred()
+ .keySet());
+ unresolvedReferences.removeAll(analyzer.getImports().keySet());
+ unresolvedReferences.removeAll(analyzer.getContained().keySet());
+
+ // Remove any java.** packages.
+ for (Iterator<PackageRef> p = unresolvedReferences.iterator(); p.hasNext();) {
+ PackageRef pack = p.next();
+ if (pack.isJava())
+ p.remove();
+ else {
+ // Remove any dynamic imports
+ if (isDynamicImport(pack))
+ p.remove();
+ }
+ }
+
+ if (!unresolvedReferences.isEmpty()) {
+ // Now we want to know the
+ // classes that are the culprits
+ Set<String> culprits = new HashSet<String>();
+ for (Clazz clazz : analyzer.getClassspace().values()) {
+ if (hasOverlap(unresolvedReferences, clazz.getReferred()))
+ culprits.add(clazz.getAbsolutePath());
+ }
+
+ error("Unresolved references to %s by class(es) %s on the Bundle-Classpath: %s",
+ unresolvedReferences, culprits, analyzer.getBundleClasspath().keySet());
+ }
+ }
+
+ /**
+ * @param p
+ * @param pack
+ */
+ private boolean isDynamicImport(PackageRef pack) {
+ if (dynamicImports == null)
+ dynamicImports = new Instructions(main.getDynamicImportPackage());
+
+ return dynamicImports.matches(pack.getFQN());
+ }
+
+ private boolean hasOverlap(Set<?> a, Set<?> b) {
+ for (Iterator<?> i = a.iterator(); i.hasNext();) {
+ if (b.contains(i.next()))
+ return true;
+ }
+ return false;
+ }
+
+ public void verify() throws Exception {
+ verifyHeaders();
+ verifyDirectives("Export-Package",
+ "uses:|mandatory:|include:|exclude:|" + IMPORT_DIRECTIVE, PACKAGEPATTERN, "package");
+ verifyDirectives("Import-Package", "resolution:", PACKAGEPATTERN, "package");
+ verifyDirectives("Require-Bundle", "visibility:|resolution:", SYMBOLICNAME, "bsn");
+ verifyDirectives("Fragment-Host", "extension:", SYMBOLICNAME, "bsn");
+ verifyDirectives("Provide-Capability", "effective:|uses:", null, null);
+ verifyDirectives("Require-Capability", "effective:|resolution:|filter:", null,null);
+ verifyDirectives("Bundle-SymbolicName", "singleton:|fragment-attachment:|mandatory:",
+ SYMBOLICNAME,"bsn");
+
+ verifyManifestFirst();
+ verifyActivator();
+ verifyActivationPolicy();
+ verifyComponent();
+ verifyNative();
+ verifyUnresolvedReferences();
+ verifySymbolicName();
+ verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
+ verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
+ verifyHeader("Bundle-Version", VERSION, true);
+ verifyListHeader("Bundle-Classpath", FILE, false);
+ verifyDynamicImportPackage();
+ verifyBundleClasspath();
+ verifyUses();
+ if (usesRequire) {
+ if (!getErrors().isEmpty()) {
+ getWarnings()
+ .add(0,
+ "Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
+ }
+ }
+
+ verifyRequirements();
+ verifyCapabilities();
+ }
+
+ private void verifyRequirements() {
+ Parameters map = parseHeader(manifest.getMainAttributes().getValue(
+ Constants.REQUIRE_CAPABILITY));
+ for (String key : map.keySet()) {
+ Attrs attrs = map.get(key);
+ verify(attrs, "filter:", FILTERPATTERN, false, "Requirement %s filter not correct", key);
+ verify(attrs, "cardinality:", CARDINALITY_PATTERN, false,
+ "Requirement %s cardinality not correct", key);
+ verify(attrs, "resolution:", RESOLUTION_PATTERN, false,
+ "Requirement %s resolution not correct", key);
+
+ if (key.equals("osgi.extender")) {
+ // No requirements on extender
+ } else if (key.equals("osgi.serviceloader")) {
+ verify(attrs, "register:", PACKAGEPATTERN, false,
+ "Service Loader extender register: directive not a fully qualified Java name");
+ } else if (key.equals("osgi.contract")) {
+
+ } else if (key.equals("osgi.service")) {
+
+ } else if (key.equals("osgi.ee")) {
+
+ } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+ error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+ }
+
+ verifyAttrs(attrs);
+
+ if (attrs.containsKey("mandatory:"))
+ error("mandatory: directive is intended for Capabilities, not Requirement %s", key);
+
+ if (attrs.containsKey("uses:"))
+ error("uses: directive is intended for Capabilities, not Requirement %s", key);
+ }
+ }
+
+ /**
+ * @param attrs
+ */
+ void verifyAttrs(Attrs attrs) {
+ for (String a : attrs.keySet()) {
+ String v = attrs.get(a);
+
+ if (!a.endsWith(":")) {
+ Attrs.Type t = attrs.getType(a);
+ if ("version".equals(a)) {
+ if (t != Attrs.Type.VERSION)
+ error("Version attributes should always be of type version, it is %s", t);
+ } else
+ verifyType(t, v);
+ }
+ }
+ }
+
+ private void verifyCapabilities() {
+ Parameters map = parseHeader(manifest.getMainAttributes().getValue(
+ Constants.PROVIDE_CAPABILITY));
+ for (String key : map.keySet()) {
+ Attrs attrs = map.get(key);
+ verify(attrs, "cardinality:", CARDINALITY_PATTERN, false,
+ "Requirement %s cardinality not correct", key);
+ verify(attrs, "resolution:", RESOLUTION_PATTERN, false,
+ "Requirement %s resolution not correct", key);
+
+ if (key.equals("osgi.extender")) {
+ verify(attrs, "osgi.extender", SYMBOLICNAME, true,
+ "Extender %s must always have the osgi.extender attribute set", key);
+ verify(attrs, "version", VERSION, true, "Extender %s must always have a version",
+ key);
+ } else if (key.equals("osgi.serviceloader")) {
+ verify(attrs, "register:", PACKAGEPATTERN, false,
+ "Service Loader extender register: directive not a fully qualified Java name");
+ } else if (key.equals("osgi.contract")) {
+ verify(attrs, "osgi.contract", SYMBOLICNAME, true,
+ "Contracts %s must always have the osgi.contract attribute set", key);
+
+ } else if (key.equals("osgi.service")) {
+ verify(attrs, "objectClass", PACKAGEPATTERN, true,
+ "osgi.service %s must have the objectClass attribute set", key);
+
+ } else if (key.equals("osgi.ee")) {
+ // TODO
+ } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+ error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+ }
+
+ verifyAttrs(attrs);
+
+ if (attrs.containsKey("filter:"))
+ error("filter: directive is intended for Requirements, not Capability %s", key);
+ if (attrs.containsKey("cardinality:"))
+ error("cardinality: directive is intended for Requirements, not Capability %s", key);
+ if (attrs.containsKey("resolution:"))
+ error("resolution: directive is intended for Requirements, not Capability %s", key);
+ }
+ }
+
+ private void verify(Attrs attrs, String ad, Pattern pattern, boolean mandatory, String msg,
+ String... args) {
+ String v = attrs.get(ad);
+ if (v == null) {
+ if (mandatory)
+ error("Missing required attribute/directive %s", ad);
+ } else {
+ Matcher m = pattern.matcher(v);
+ if (!m.matches())
+ error(msg, (Object[]) args);
+ }
+ }
+
+ private void verifyType(Attrs.Type type, String string) {
+
+ }
+
+ /**
+ * Verify if the header does not contain any other directives
+ *
+ * @param header
+ * @param directives
+ */
+ private void verifyDirectives(String header, String directives, Pattern namePattern, String type) {
+ Pattern pattern = Pattern.compile(directives);
+ Parameters map = parseHeader(manifest.getMainAttributes().getValue(header));
+ for (Entry<String, Attrs> entry : map.entrySet()) {
+ String pname = removeDuplicateMarker(entry.getKey());
+
+ if (namePattern != null) {
+ if (!namePattern.matcher(pname).matches())
+ if (isPedantic())
+ error("Invalid %s name: '%s'", type, pname);
+ else
+ warning("Invalid %s name: '%s'", type, pname);
+ }
+
+ for (String key : entry.getValue().keySet()) {
+ if (key.endsWith(":")) {
+ if (!key.startsWith("x-")) {
+ Matcher m = pattern.matcher(key);
+ if (m.matches())
+ continue;
+
+ warning("Unknown directive %s in %s, allowed directives are %s, and 'x-*'.",
+ key, header, directives.replace('|', ','));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Verify the use clauses
+ */
+ private void verifyUses() {
+ // Set<String> uses = Create.set();
+ // for ( Map<String,String> attrs : analyzer.getExports().values()) {
+ // if ( attrs.containsKey(Constants.USES_DIRECTIVE)) {
+ // String s = attrs.get(Constants.USES_DIRECTIVE);
+ // uses.addAll( split(s));
+ // }
+ // }
+ // uses.removeAll(analyzer.getExports().keySet());
+ // uses.removeAll(analyzer.getImports().keySet());
+ // if ( !uses.isEmpty())
+ // warning("Export-Package uses: directive contains packages that are not imported nor exported: %s",
+ // uses);
+ }
+
+ public boolean verifyActivationPolicy() {
+ String policy = main.get(Constants.BUNDLE_ACTIVATIONPOLICY);
+ if (policy == null)
+ return true;
+
+ return verifyActivationPolicy(policy);
+ }
+
+ public boolean verifyActivationPolicy(String policy) {
+ Parameters map = parseHeader(policy);
+ if (map.size() == 0)
+ warning("Bundle-ActivationPolicy is set but has no argument %s", policy);
+ else if (map.size() > 1)
+ warning("Bundle-ActivationPolicy has too many arguments %s", policy);
+ else {
+ Map<String, String> s = map.get("lazy");
+ if (s == null)
+ warning("Bundle-ActivationPolicy set but is not set to lazy: %s", policy);
+ else
+ return true;
+ }
+
+ return false;
+ }
+
+ public void verifyBundleClasspath() {
+ Parameters bcp = main.getBundleClassPath();
+ if (bcp.isEmpty() || bcp.containsKey("."))
+ return;
+
+ for (String path : bcp.keySet()) {
+ if (path.endsWith("/"))
+ error("A Bundle-ClassPath entry must not end with '/': %s", path);
+
+ if (dot.getDirectories().containsKey(path))
+ // We assume that any classes are in a directory
+ // and therefore do not care when the bundle is included
+ return;
+ }
+
+ for (String path : dot.getResources().keySet()) {
+ if (path.endsWith(".class")) {
+ warning("The Bundle-Classpath does not contain the actual bundle JAR (as specified with '.' in the Bundle-Classpath) but the JAR does contain classes. Is this intentional?");
+ return;
+ }
+ }
+ }
+
+ /**
+ * <pre>
+ * DynamicImport-Package ::= dynamic-description
+ * ( ',' dynamic-description )*
+ *
+ * dynamic-description::= wildcard-names ( ';' parameter )*
+ * wildcard-names ::= wildcard-name ( ';' wildcard-name )*
+ * wildcard-name ::= package-name
+ * | ( package-name '.*' ) // See 1.4.2
+ * | '*'
+ * </pre>
+ */
+ private void verifyDynamicImportPackage() {
+ verifyListHeader("DynamicImport-Package", WILDCARDPACKAGE, true);
+ String dynamicImportPackage = get("DynamicImport-Package");
+ if (dynamicImportPackage == null)
+ return;
+
+ Parameters map = main.getDynamicImportPackage();
+ for (String name : map.keySet()) {
+ name = name.trim();
+ if (!verify(name, WILDCARDPACKAGE))
+ error("DynamicImport-Package header contains an invalid package name: " + name);
+
+ Map<String, String> sub = map.get(name);
+ if (r3 && sub.size() != 0) {
+ error("DynamicPackage-Import has attributes on import: "
+ + name
+ + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
+ }
+ }
+ }
+
+ private void verifyManifestFirst() {
+ if (!dot.isManifestFirst()) {
+ error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
+ }
+ }
+
+ private void verifySymbolicName() {
+ Parameters bsn = parseHeader(main.get(Analyzer.BUNDLE_SYMBOLICNAME));
+ if (!bsn.isEmpty()) {
+ if (bsn.size() > 1)
+ error("More than one BSN specified " + bsn);
+
+ String name = bsn.keySet().iterator().next();
+ if (!isBsn(name)) {
+ error("Symbolic Name has invalid format: " + name);
+ }
+ }
+ }
+
+ /**
+ * @param name
+ * @return
+ */
+ public static boolean isBsn(String name) {
+ return SYMBOLICNAME.matcher(name).matches();
+ }
+
+ /**
+ * <pre>
+ * filter ::= ’(’ filter-comp ’)’
+ * filter-comp ::= and | or | not | operation
+ * and ::= ’&’ filter-list
+ * or ::= ’|’ filter-list
+ * not ::= ’!’ filter
+ * filter-list ::= filter | filter filter-list
+ * operation ::= simple | present | substring
+ * simple ::= attr filter-type value
+ * filter-type ::= equal | approx | greater | less
+ * equal ::= ’=’
+ * approx ::= ’˜=’
+ * greater ::= ’>=’
+ * less ::= ’<=’
+ * present ::= attr ’=*’
+ * substring ::= attr ’=’ initial any final
+ * inital ::= () | value
+ * any ::= ’*’ star-value
+ * star-value ::= () | value ’*’ star-value
+ * final ::= () | value
+ * value ::= <see text>
+ * </pre>
+ *
+ * @param expr
+ * @param index
+ * @return
+ */
+
+ public static int verifyFilter(String expr, int index) {
+ try {
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ if (expr.charAt(index) != '(')
+ throw new IllegalArgumentException("Filter mismatch: expected ( at position "
+ + index + " : " + expr);
+
+ index++; // skip (
+
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ switch (expr.charAt(index)) {
+ case '!':
+ index++; // skip !
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ if (expr.charAt(index) != '(')
+ throw new IllegalArgumentException(
+ "Filter mismatch: ! (not) must have one sub expression " + index
+ + " : " + expr);
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ index = verifyFilter(expr, index);
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+ if (expr.charAt(index) != ')')
+ throw new IllegalArgumentException("Filter mismatch: expected ) at position "
+ + index + " : " + expr);
+ return index + 1;
+
+ case '&':
+ case '|':
+ index++; // skip operator
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+ while (expr.charAt(index) == '(') {
+ index = verifyFilter(expr, index);
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+ }
+
+ if (expr.charAt(index) != ')')
+ throw new IllegalArgumentException("Filter mismatch: expected ) at position "
+ + index + " : " + expr);
+ return index + 1; // skip )
+
+ default:
+ index = verifyFilterOperation(expr, index);
+ if (expr.charAt(index) != ')')
+ throw new IllegalArgumentException("Filter mismatch: expected ) at position "
+ + index + " : " + expr);
+ return index + 1;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("Filter mismatch: early EOF from " + index);
+ }
+ }
+
+ static private int verifyFilterOperation(String expr, int index) {
+ StringBuilder sb = new StringBuilder();
+ while ("=><~()".indexOf(expr.charAt(index)) < 0) {
+ sb.append(expr.charAt(index++));
+ }
+ String attr = sb.toString().trim();
+ if (attr.length() == 0)
+ throw new IllegalArgumentException("Filter mismatch: attr at index " + index + " is 0");
+ sb = new StringBuilder();
+ while ("=><~".indexOf(expr.charAt(index)) >= 0) {
+ sb.append(expr.charAt(index++));
+ }
+ String operator = sb.toString();
+ if (!verify(operator, FILTEROP))
+ throw new IllegalArgumentException("Filter error, illegal operator " + operator
+ + " at index " + index);
+
+ sb = new StringBuilder();
+ while (")".indexOf(expr.charAt(index)) < 0) {
+ switch (expr.charAt(index)) {
+ case '\\':
+ if ("\\)(*".indexOf(expr.charAt(index + 1)) >= 0)
+ index++;
+ else
+ throw new IllegalArgumentException(
+ "Filter error, illegal use of backslash at index " + index
+ + ". Backslash may only be used before * or () or \\");
+ }
+ sb.append(expr.charAt(index++));
+ }
+ return index;
+ }
+
+ private boolean verifyHeader(String name, Pattern regex, boolean error) {
+ String value = manifest.getMainAttributes().getValue(name);
+ if (value == null)
+ return false;
+
+ QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
+ for (Iterator<String> i = st.getTokenSet().iterator(); i.hasNext();) {
+ if (!verify(i.next(), regex)) {
+ String msg = "Invalid value for " + name + ", " + value + " does not match "
+ + regex.pattern();
+ if (error)
+ error(msg);
+ else
+ warning(msg);
+ }
+ }
+ return true;
+ }
+
+ static private boolean verify(String value, Pattern regex) {
+ return regex.matcher(value).matches();
+ }
+
+ private boolean verifyListHeader(String name, Pattern regex, boolean error) {
+ String value = manifest.getMainAttributes().getValue(name);
+ if (value == null)
+ return false;
+
+ Parameters map = parseHeader(value);
+ for (String header : map.keySet()) {
+ if (!regex.matcher(header).matches()) {
+ String msg = "Invalid value for " + name + ", " + value + " does not match "
+ + regex.pattern();
+ if (error)
+ error(msg);
+ else
+ warning(msg);
+ }
+ }
+ return true;
+ }
+
+ public String getProperty(String key, String deflt) {
+ if (properties == null)
+ return deflt;
+ return properties.getProperty(key, deflt);
+ }
+
+ public static boolean isVersion(String version) {
+ return VERSION.matcher(version).matches();
+ }
+
+ public static boolean isIdentifier(String value) {
+ if (value.length() < 1)
+ return false;
+
+ if (!Character.isJavaIdentifierStart(value.charAt(0)))
+ return false;
+
+ for (int i = 1; i < value.length(); i++) {
+ if (!Character.isJavaIdentifierPart(value.charAt(i)))
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isMember(String value, String[] matches) {
+ for (String match : matches) {
+ if (match.equals(value))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isFQN(String name) {
+ if (name.length() == 0)
+ return false;
+ if (!Character.isJavaIdentifierStart(name.charAt(0)))
+ return false;
+
+ for (int i = 1; i < name.length(); i++) {
+ char c = name.charAt(i);
+ if (Character.isJavaIdentifierPart(c) || c == '$' || c == '.')
+ continue;
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Verify checksums
+ */
+ /**
+ * Verify the checksums from the manifest against the real thing.
+ *
+ * @param all
+ * if each resources must be digested
+ * @return true if ok
+ * @throws Exception
+ */
+
+ public void verifyChecksums(boolean all) throws Exception {
+ Manifest m = dot.getManifest();
+ if (m == null || m.getEntries().isEmpty()) {
+ if (all)
+ error("Verify checksums with all but no digests");
+ return;
+ }
+
+ List<String> missingDigest = new ArrayList<String>();
+
+ for (String path : dot.getResources().keySet()) {
+ if (path.equals("META-INF/MANIFEST.MF"))
+ continue;
+
+ Attributes a = m.getAttributes(path);
+ String digest = a.getValue("SHA1-Digest");
+ if (digest == null) {
+ if (!path.matches(""))
+ missingDigest.add(path);
+ } else {
+ byte[] d = Base64.decodeBase64(digest);
+ SHA1 expected = new SHA1(d);
+ Digester<SHA1> digester = SHA1.getDigester();
+ InputStream in = dot.getResource(path).openInputStream();
+ IO.copy(in, digester);
+ digester.digest();
+ if (!expected.equals(digester.digest())) {
+ error("Checksum mismatch %s, expected %s, got %s", path, expected,
+ digester.digest());
+ }
+ }
+ }
+ if (missingDigest.size() > 0) {
+ error("Entries in the manifest are missing digests: %s", missingDigest);
+ }
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java
new file mode 100644
index 0000000..2acbe95
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java
@@ -0,0 +1,68 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public abstract class WriteResource implements Resource {
+ String extra;
+ volatile long size = -1;
+
+ public InputStream openInputStream() throws Exception {
+ PipedInputStream pin = new PipedInputStream();
+ final PipedOutputStream pout = new PipedOutputStream(pin);
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ write(pout);
+ pout.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ pout.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ };
+ t.start();
+ return pin;
+ }
+
+ public abstract void write(OutputStream out) throws IOException, Exception;
+
+ public abstract long lastModified();
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ static class CountingOutputStream extends OutputStream {
+ long size;
+
+ @Override public void write(int var0) throws IOException {
+ size++;
+ }
+
+ @Override public void write(byte[] buffer) throws IOException {
+ size+=buffer.length;
+ }
+
+ @Override public void write(byte [] buffer, int start, int length) throws IOException {
+ size+=length;
+ }
+ }
+
+ public long size() throws IOException, Exception {
+ if ( size == -1 ) {
+ CountingOutputStream cout = new CountingOutputStream();
+ write(cout);
+ size = cout.size;
+ }
+ return size;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java
new file mode 100755
index 0000000..126faba
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java
@@ -0,0 +1,88 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+public class ZipResource implements Resource {
+ ZipFile zip;
+ ZipEntry entry;
+ long lastModified;
+ String extra;
+
+ ZipResource(ZipFile zip, ZipEntry entry, long lastModified) throws UnsupportedEncodingException {
+ this.zip = zip;
+ this.entry = entry;
+ this.lastModified = lastModified;
+ byte[] data = entry.getExtra();
+ if (data != null)
+ this.extra = new String(data, "UTF-8");
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return zip.getInputStream(entry);
+ }
+
+ public String toString() {
+ return ":" + zip.getName() + "(" + entry.getName() + "):";
+ }
+
+ public static ZipFile build(Jar jar, File file) throws ZipException,
+ IOException {
+ return build(jar, file, null);
+ }
+
+ public static ZipFile build(Jar jar, File file, Pattern pattern)
+ throws ZipException, IOException {
+
+ try {
+ ZipFile zip = new ZipFile(file);
+ nextEntry: for (Enumeration<? extends ZipEntry> e = zip.entries(); e
+ .hasMoreElements();) {
+ ZipEntry entry = e.nextElement();
+ if (pattern != null) {
+ Matcher m = pattern.matcher(entry.getName());
+ if (!m.matches())
+ continue nextEntry;
+ }
+ if (!entry.isDirectory()) {
+ long time = entry.getTime();
+ if (time <= 0)
+ time = file.lastModified();
+ jar.putResource(entry.getName(), new ZipResource(zip,
+ entry, time), true);
+ }
+ }
+ return zip;
+ } catch (ZipException ze) {
+ throw new ZipException("The JAR/ZIP file ("
+ + file.getAbsolutePath() + ") seems corrupted, error: "
+ + ze.getMessage());
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("Problem opening JAR: "
+ + file.getAbsolutePath());
+ }
+ }
+
+ public void write(OutputStream out) throws Exception {
+ FileResource.copy(this, out);
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+
+ public long size() {
+ return entry.getSize();
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/bnd.info b/bundleplugin/src/main/java/aQute/lib/osgi/bnd.info
new file mode 100644
index 0000000..b1706a5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/bnd.info
@@ -0,0 +1,2 @@
+modified=${currenttime}
+version=${Bundle-Version}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java b/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java
new file mode 100755
index 0000000..6e01ddc
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java
@@ -0,0 +1,248 @@
+package aQute.lib.osgi.eclipse;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.parsers.*;
+
+import org.w3c.dom.*;
+import org.xml.sax.*;
+
+import aQute.libg.reporter.*;
+
+/**
+ * Parse the Eclipse project information for the classpath. Unfortunately, it is
+ * impossible to read the variables. They are ignored but that can cause
+ * problems.
+ *
+ * @version $Revision$
+ */
+public class EclipseClasspath {
+ static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+ .newInstance();
+ DocumentBuilder db;
+ File project;
+ File workspace;
+ Set<File> sources = new LinkedHashSet<File>();
+ Set<File> allSources = new LinkedHashSet<File>();
+
+ Set<File> classpath = new LinkedHashSet<File>();
+ List<File> dependents = new ArrayList<File>();
+ File output;
+ boolean recurse = true;
+ Set<File> exports = new LinkedHashSet<File>();
+ Map<String, String> properties = new HashMap<String, String>();
+ Reporter reporter;
+ int options;
+ Set<File> bootclasspath = new LinkedHashSet<File>();
+
+ public final static int DO_VARIABLES = 1;
+
+ /**
+ * Parse an Eclipse project structure to discover the classpath.
+ *
+ * @param workspace
+ * Points to workspace
+ * @param project
+ * Points to project
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ */
+
+ public EclipseClasspath(Reporter reporter, File workspace, File project,
+ int options) throws Exception {
+ this.project = project.getCanonicalFile();
+ this.workspace = workspace.getCanonicalFile();
+ this.reporter = reporter;
+ db = documentBuilderFactory.newDocumentBuilder();
+ parse(this.project, true);
+ db = null;
+ }
+
+ public EclipseClasspath(Reporter reporter, File workspace, File project)
+ throws Exception {
+ this(reporter, workspace, project, 0);
+ }
+
+ /**
+ * Recursive routine to parse the files. If a sub project is detected, it is
+ * parsed before the parsing continues. This should give the right order.
+ *
+ * @param project
+ * Project directory
+ * @param top
+ * If this is the top project
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ */
+ void parse(File project, boolean top) throws ParserConfigurationException,
+ SAXException, IOException {
+ File file = new File(project, ".classpath");
+ if (!file.exists())
+ throw new FileNotFoundException(".classpath file not found: "
+ + file.getAbsolutePath());
+
+ Document doc = db.parse(file);
+ NodeList nodelist = doc.getDocumentElement().getElementsByTagName(
+ "classpathentry");
+
+ if (nodelist == null)
+ throw new IllegalArgumentException(
+ "Can not find classpathentry in classpath file");
+
+ for (int i = 0; i < nodelist.getLength(); i++) {
+ Node node = nodelist.item(i);
+ NamedNodeMap attrs = node.getAttributes();
+ String kind = get(attrs, "kind");
+ if ("src".equals(kind)) {
+ String path = get(attrs, "path");
+ // TODO boolean exported = "true".equalsIgnoreCase(get(attrs,
+ // "exported"));
+ if (path.startsWith("/")) {
+ // We have another project
+ File subProject = getFile(workspace, project, path);
+ if (recurse)
+ parse(subProject, false);
+ dependents.add(subProject.getCanonicalFile());
+ } else {
+ File src = getFile(workspace, project, path);
+ allSources.add(src);
+ if (top) {
+ // We only want the sources for our own project
+ // or we'll compile all at once. Not a good idea
+ // because project settings can differ.
+ sources.add(src);
+ }
+ }
+ } else if ("lib".equals(kind)) {
+ String path = get(attrs, "path");
+ boolean exported = "true".equalsIgnoreCase(get(attrs,
+ "exported"));
+ if (top || exported) {
+ File jar = getFile(workspace, project, path);
+ if (jar.getName().startsWith("ee."))
+ bootclasspath.add(jar);
+ else
+ classpath.add(jar);
+ if (exported)
+ exports.add(jar);
+ }
+ } else if ("output".equals(kind)) {
+ String path = get(attrs, "path");
+ path = path.replace('/', File.separatorChar);
+ output = getFile(workspace, project, path);
+ classpath.add(output);
+ exports.add(output);
+ } else if ("var".equals(kind)) {
+ boolean exported = "true".equalsIgnoreCase(get(attrs,
+ "exported"));
+ File lib = replaceVar(get(attrs, "path"));
+ File slib = replaceVar(get(attrs, "sourcepath"));
+ if (lib != null) {
+ classpath.add(lib);
+ if (exported)
+ exports.add(lib);
+ }
+ if (slib != null)
+ sources.add(slib);
+ } else if ("con".equals(kind)) {
+ // Should do something useful ...
+ }
+ }
+ }
+
+ private File getFile(File abs, File relative, String opath) {
+ String path = opath.replace('/', File.separatorChar);
+ File result = new File(path);
+ if (result.isAbsolute() && result.isFile()) {
+ return result;
+ }
+ if (path.startsWith(File.separator)) {
+ result = abs;
+ path = path.substring(1);
+ } else
+ result = relative;
+
+ StringTokenizer st = new StringTokenizer(path, File.separator);
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ result = new File(result, token);
+ }
+
+ if (!result.exists())
+ System.err.println("File not found: project=" + project
+ + " workspace=" + workspace + " path=" + opath + " file="
+ + result);
+ return result;
+ }
+
+ static Pattern PATH = Pattern.compile("([A-Z_]+)/(.*)");
+
+ private File replaceVar(String path) {
+ if ((options & DO_VARIABLES) == 0)
+ return null;
+
+ Matcher m = PATH.matcher(path);
+ if (m.matches()) {
+ String var = m.group(1);
+ String remainder = m.group(2);
+ String base = properties.get(var);
+ if (base != null) {
+ File b = new File(base);
+ File f = new File(b, remainder.replace('/', File.separatorChar));
+ return f;
+ } else
+ reporter.error("Can't find replacement variable for: " + path);
+ } else
+ reporter.error("Cant split variable path: " + path);
+ return null;
+ }
+
+ private String get(NamedNodeMap map, String name) {
+ Node node = map.getNamedItem(name);
+ if (node == null)
+ return null;
+
+ return node.getNodeValue();
+ }
+
+ public Set<File> getClasspath() {
+ return classpath;
+ }
+
+ public Set<File> getSourcepath() {
+ return sources;
+ }
+
+ public File getOutput() {
+ return output;
+ }
+
+ public List<File> getDependents() {
+ return dependents;
+ }
+
+ public void setRecurse(boolean recurse) {
+ this.recurse = recurse;
+ }
+
+ public Set<File> getExports() {
+ return exports;
+ }
+
+ public void setProperties(Map<String, String> map) {
+ this.properties = map;
+ }
+
+ public Set<File> getBootclasspath() {
+ return bootclasspath;
+ }
+
+ public Set<File> getAllSources() {
+ return allSources;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/packageinfo b/bundleplugin/src/main/java/aQute/lib/osgi/packageinfo
new file mode 100644
index 0000000..084a0d4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/packageinfo
@@ -0,0 +1 @@
+version 2.0.0
diff --git a/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java b/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java
new file mode 100644
index 0000000..5bd8178
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java
@@ -0,0 +1,281 @@
+package aQute.lib.putjar;
+
+import java.io.*;
+import java.util.zip.*;
+
+import aQute.libg.fileiterator.*;
+
+public class DirectoryInputStream extends InputStream {
+ final File root;
+ final FileIterator fi;
+ File element;
+ int entries = 0;
+ int state = START;
+ long where = 0;
+
+ final static int START = 0;
+ final static int HEADER = 1;
+ final static int DATA = 2;
+ final static int DIRECTORY = 4;
+ final static int EOF = 5;
+
+ final static InputStream eof = new ByteArrayInputStream(new byte[0]);
+ ByteArrayOutputStream directory = new ByteArrayOutputStream();
+ InputStream current = eof;
+
+ public DirectoryInputStream(File dir) {
+ root = dir;
+ fi = new FileIterator(dir);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (fi == null)
+ return -1;
+
+ int c = current.read();
+ if (c < 0) {
+ next();
+ c = current.read();
+ }
+ if (c >= 0)
+ where++;
+
+ return c;
+ }
+
+ void next() throws IOException {
+ switch (state) {
+ case START:
+ case DATA:
+ nextHeader();
+ break;
+
+ case HEADER:
+ if (element.isFile() && element.length() > 0) {
+ current = new FileInputStream(element);
+ state = DATA;
+ } else
+ nextHeader();
+ break;
+
+ case DIRECTORY:
+ state = EOF;
+ current = eof;
+ break;
+
+ case EOF:
+ break;
+ }
+ }
+
+ private void nextHeader() throws IOException {
+ if (fi.hasNext()) {
+ element = fi.next();
+ state = HEADER;
+ current = getHeader(root, element);
+ entries++;
+ } else {
+ current = getDirectory();
+ state = DIRECTORY;
+ }
+ }
+
+ /**
+ * <pre>
+ * end of central dir signature 4 bytes (0x06054b50)
+ * number of this disk 2 bytes
+ * number of the disk with the
+ * start of the central directory 2 bytes
+ * total number of entries in the
+ * central directory on this disk 2 bytes
+ * total number of entries in
+ * the central directory 2 bytes
+ * size of the central directory 4 bytes
+ * offset of start of central
+ * directory with respect to
+ * the starting disk number 4 bytes
+ * .ZIP file comment length 2 bytes
+ * .ZIP file comment (variable size)
+ * </pre>
+ *
+ * @return
+ */
+ InputStream getDirectory() throws IOException {
+ long where = this.where;
+ int sizeDirectory = directory.size();
+
+ writeInt(directory, 0x504b0506); // Signature
+ writeShort(directory, 0); // # of disk
+ writeShort(directory, 0); // # of the disk with start of the central
+ // dir
+ writeShort(directory, entries); // # of entries
+ writeInt(directory, sizeDirectory); // Size of central dir
+ writeInt(directory, (int) where);
+ writeShort(directory, 0);
+
+ directory.close();
+
+ byte[] data = directory.toByteArray();
+ return new ByteArrayInputStream(data);
+ }
+
+ private void writeShort(OutputStream out, int v) throws IOException {
+ for (int i = 0; i < 2; i++) {
+ out.write((byte) (v & 0xFF));
+ v = v >> 8;
+ }
+ }
+
+ private void writeInt(OutputStream out, int v) throws IOException {
+ for (int i = 0; i < 4; i++) {
+ out.write((byte) (v & 0xFF));
+ v = v >> 8;
+ }
+ }
+
+ /**
+ * Local file header:
+ *
+ * <pre>
+ *
+ * local file header signature 4 bytes (0x04034b50)
+ * version needed to extract 2 bytes
+ * general purpose bit flag 2 bytes
+ * compression method 2 bytes
+ * last mod file time 2 bytes
+ * last mod file date 2 bytes
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ * file name length 2 bytes
+ * extra field length 2 bytes
+ *
+ * file name (variable size)
+ * extra field (variable size)
+ *
+ * central file header signature 4 bytes (0x02014b50)
+ * version made by 2 bytes
+ * version needed to extract 2 bytes
+ * general purpose bit flag 2 bytes
+ * compression method 2 bytes
+ * last mod file time 2 bytes
+ * last mod file date 2 bytes
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ * file name length 2 bytes
+ * extra field length 2 bytes
+ * file comment length 2 bytes
+ * disk number start 2 bytes
+ * internal file attributes 2 bytes
+ * external file attributes 4 bytes
+ * relative offset of local header 4 bytes
+ *
+ * file name (variable size)
+ * extra field (variable size)
+ * file comment (variable size)
+ * </pre>
+ * </pre>
+ *
+ * @param file
+ * @return
+ */
+ private InputStream getHeader(File root, File file) throws IOException {
+ long where = this.where;
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ // Signature
+ writeInt(bout, 0x04034b50);
+ writeInt(directory, 0x504b0102);
+
+ // Version needed to extract
+ writeShort(directory, 0);
+
+ // Version needed to extract
+ writeShort(bout, 10);
+ writeShort(directory, 10);
+
+ // General purpose bit flag (use descriptor)
+ writeShort(bout, 0); // descriptor follows data
+ writeShort(directory, 0); // descriptor follows data
+
+ // Compresson method (stored)
+ writeShort(bout, 0);
+ writeShort(directory, 0);
+
+ // Mod time
+ writeInt(bout, 0);
+ writeInt(directory, 0);
+
+ if (file.isDirectory()) {
+ writeInt(bout, 0); // CRC
+ writeInt(bout, 0); // Compressed size
+ writeInt(bout, 0); // Uncompressed Size
+ writeInt(directory, 0);
+ writeInt(directory, 0);
+ writeInt(directory, 0);
+ } else {
+ CRC32 crc = getCRC(file);
+ writeInt(bout, (int) crc.getValue());
+ writeInt(bout, (int) file.length());
+ writeInt(bout, (int) file.length());
+ writeInt(directory, (int) crc.getValue());
+ writeInt(directory, (int) file.length());
+ writeInt(directory, (int) file.length());
+ }
+
+ String p = getPath(root, file);
+ if (file.isDirectory())
+ p = p + "/";
+ byte[] path = p.getBytes("UTF-8");
+ writeShort(bout, path.length);
+ writeShort(directory, path.length);
+
+ writeShort(bout, 0); // extra length
+ writeShort(directory, 0);
+
+ bout.write(path);
+
+ writeShort(directory, 0); // File comment length
+ writeShort(directory, 0); // disk number start 2 bytes
+ writeShort(directory, 0); // internal file attributes 2 bytes
+ writeInt(directory, 0); // external file attributes 4 bytes
+ writeInt(directory, (int) where); // relative offset of local header 4
+ // bytes
+
+ directory.write(path);
+
+ byte[] bytes = bout.toByteArray();
+ return new ByteArrayInputStream(bytes);
+ }
+
+ private String getPath(File root, File file) {
+ if (file.equals(root))
+ return "";
+
+ String p = getPath(root, file.getParentFile());
+ if (p.length() == 0)
+ p = file.getName();
+ else {
+ p = p + "/" + file.getName();
+ }
+ return p;
+ }
+
+ private CRC32 getCRC(File file) throws IOException {
+ CRC32 crc = new CRC32();
+ FileInputStream in = new FileInputStream(file);
+ try {
+ byte data[] = new byte[10000];
+ int size = in.read(data);
+ while (size > 0) {
+ crc.update(data, 0, size);
+ size = in.read(data);
+ }
+ } finally {
+ in.close();
+ }
+ return crc;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/putjar/packageinfo b/bundleplugin/src/main/java/aQute/lib/putjar/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/putjar/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java b/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
new file mode 100644
index 0000000..d9c60cd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
@@ -0,0 +1,24 @@
+package aQute.lib.spring;
+
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+/**
+ * This component is called when we find a resource in the META-INF/*.xml
+ * pattern. We parse the resource and and the imports to the builder.
+ *
+ * Parsing is done with XSLT (first time I see the use of having XML for the
+ * Spring configuration files!).
+ *
+ * @author aqute
+ *
+ */
+public class JPAComponent extends XMLTypeProcessor {
+
+ protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
+ List<XMLType> types = new ArrayList<XMLType>();
+ process(types,"jpa.xsl", "META-INF", "persistence.xml");
+ return types;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java b/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java
new file mode 100644
index 0000000..0109ad4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java
@@ -0,0 +1,99 @@
+package aQute.lib.spring;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.regex.*;
+
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.libg.header.*;
+
+/**
+ * This component is called when we find a resource in the META-INF/*.xml
+ * pattern. We parse the resource and and the imports to the builder.
+ *
+ * Parsing is done with XSLT (first time I see the use of having XML for the
+ * Spring configuration files!).
+ *
+ * @author aqute
+ *
+ */
+public class SpringComponent implements AnalyzerPlugin {
+ static Transformer transformer;
+ static Pattern SPRING_SOURCE = Pattern.compile("META-INF/spring/.*\\.xml");
+ static Pattern QN = Pattern.compile("[_A-Za-z$][_A-Za-z0-9$]*(\\.[_A-Za-z$][_A-Za-z0-9$]*)*");
+
+ public static Set<CharSequence> analyze(InputStream in) throws Exception {
+ if (transformer == null) {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Source source = new StreamSource(SpringComponent.class
+ .getResourceAsStream("extract.xsl"));
+ transformer = tf.newTransformer(source);
+ }
+
+ Set<CharSequence> refers = new HashSet<CharSequence>();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ Result r = new StreamResult(bout);
+ Source s = new StreamSource(in);
+ transformer.transform(s, r);
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
+ bout.close();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(bin, "UTF8"));
+
+ String line = br.readLine();
+ while (line != null) {
+ line = line.trim();
+ if (line.length() > 0) {
+ String parts[] = line.split("\\s*,\\s*");
+ for (int i = 0; i < parts.length; i++) {
+ int n = parts[i].lastIndexOf('.');
+ if (n > 0) {
+ refers.add(parts[i].subSequence(0, n));
+ }
+ }
+ }
+ line = br.readLine();
+ }
+ br.close();
+ return refers;
+ }
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+ Jar jar = analyzer.getJar();
+ Map<String, Resource> dir = jar.getDirectories().get("META-INF/spring");
+ if ( dir == null || dir.isEmpty())
+ return false;
+
+ for (Iterator<Entry<String, Resource>> i = dir.entrySet().iterator(); i.hasNext();) {
+ Entry<String, Resource> entry = i.next();
+ String path = entry.getKey();
+ Resource resource = entry.getValue();
+ if (SPRING_SOURCE.matcher(path).matches()) {
+ try {
+ InputStream in = resource.openInputStream();
+ Set<CharSequence> set = analyze(in);
+ in.close();
+ for (Iterator<CharSequence> r = set.iterator(); r.hasNext();) {
+ PackageRef pack = analyzer.getPackageRef((String) r.next());
+ if ( !QN.matcher(pack.getFQN()).matches())
+ analyzer.warning("Package does not seem a package in spring resource ("+path+"): " + pack );
+ if (!analyzer.getReferred().containsKey(pack))
+ analyzer.getReferred().put(pack, new Attrs());
+ }
+ } catch( Exception e ) {
+ analyzer.error("Unexpected exception in processing spring resources("+path+"): " + e );
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java b/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
new file mode 100644
index 0000000..60eb922
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
@@ -0,0 +1,33 @@
+package aQute.lib.spring;
+
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+/**
+ * This component is called when we find a resource in the META-INF/*.xml
+ * pattern. We parse the resource and and the imports to the builder.
+ *
+ * Parsing is done with XSLT (first time I see the use of having XML for the
+ * Spring configuration files!).
+ *
+ * @author aqute
+ *
+ */
+public class SpringXMLType extends XMLTypeProcessor {
+
+ protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
+ List<XMLType> types = new ArrayList<XMLType>();
+
+ String header = analyzer.getProperty("Bundle-Blueprint", "OSGI-INF/blueprint");
+ process(types,"extract.xsl", header, ".*\\.xml");
+ header = analyzer.getProperty("Spring-Context", "META-INF/spring");
+ process(types,"extract.xsl", header, ".*\\.xml");
+
+ return types;
+ }
+
+
+
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java b/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java
new file mode 100644
index 0000000..f7c6b33
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java
@@ -0,0 +1,108 @@
+package aQute.lib.spring;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Descriptors.PackageRef;
+
+public class XMLType {
+
+ Transformer transformer;
+ Pattern paths;
+ String root;
+
+
+ static Pattern QN = Pattern
+ .compile("[_A-Za-z$][_A-Za-z0-9$]*(\\.[_A-Za-z$][_A-Za-z0-9$]*)*");
+
+ public XMLType(URL source, String root, String paths ) throws Exception {
+ transformer = getTransformer(source);
+ this.paths = Pattern.compile(paths);
+ this.root = root;
+ }
+
+ public Set<String> analyze(InputStream in) throws Exception {
+ Set<String> refers = new HashSet<String>();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ Result r = new StreamResult(bout);
+ Source s = new StreamSource(in);
+ transformer.transform(s, r);
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
+ bout.close();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(bin, "UTF8"));
+
+ String line = br.readLine();
+ while (line != null) {
+ line = line.trim();
+ if (line.length() > 0) {
+ String parts[] = line.split("\\s*,\\s*");
+ for (int i = 0; i < parts.length; i++) {
+ int n = parts[i].lastIndexOf('.');
+ if (n > 0) {
+ refers.add(parts[i].subSequence(0, n).toString());
+ }
+ }
+ }
+ line = br.readLine();
+ }
+ br.close();
+ return refers;
+ }
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+ Jar jar = analyzer.getJar();
+ Map<String,Resource> dir = jar.getDirectories().get(root);
+ if (dir == null || dir.isEmpty()) {
+ Resource resource = jar.getResource(root);
+ if ( resource != null )
+ process(analyzer, root, resource);
+ return false;
+ }
+
+ for (Iterator<Map.Entry<String,Resource>> i = dir.entrySet().iterator(); i.hasNext();) {
+ Map.Entry<String,Resource> entry = i.next();
+ String path = entry.getKey();
+ Resource resource = entry.getValue();
+ if (paths.matcher(path).matches()) {
+ process(analyzer, path, resource);
+ }
+ }
+ return false;
+ }
+
+ private void process(Analyzer analyzer, String path, Resource resource) {
+ try {
+ InputStream in = resource.openInputStream();
+ Set<String> set = analyze(in);
+ in.close();
+ for (Iterator<String> r = set.iterator(); r.hasNext();) {
+ PackageRef pack = analyzer.getPackageRef(r.next());
+ if (!QN.matcher(pack.getFQN()).matches())
+ analyzer
+ .warning("Package does not seem a package in spring resource ("
+ + path + "): " + pack);
+ if (!analyzer.getReferred().containsKey(pack))
+ analyzer.getReferred().put(pack);
+ }
+ } catch (Exception e) {
+ analyzer
+ .error("Unexpected exception in processing spring resources("
+ + path + "): " + e);
+ }
+ }
+
+ protected Transformer getTransformer(java.net.URL url) throws Exception {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Source source = new StreamSource(url.openStream());
+ return tf.newTransformer(source);
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java b/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java
new file mode 100644
index 0000000..6ae0e7e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java
@@ -0,0 +1,34 @@
+package aQute.lib.spring;
+
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.header.*;
+
+public class XMLTypeProcessor implements AnalyzerPlugin {
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+ List<XMLType> types = getTypes(analyzer);
+ for ( XMLType type : types ) {
+ type.analyzeJar(analyzer);
+ }
+ return false;
+ }
+
+ protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
+ return new ArrayList<XMLType>();
+ }
+
+
+ protected void process(List<XMLType> types, String resource, String paths,
+ String pattern) throws Exception {
+
+ Parameters map = Processor.parseHeader(paths,null);
+ for ( String path : map.keySet() ) {
+ types.add( new XMLType( getClass().getResource(resource), path, pattern ));
+ }
+ }
+
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/extract.xsl b/bundleplugin/src/main/java/aQute/lib/spring/extract.xsl
new file mode 100644
index 0000000..efad6cd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/extract.xsl
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:osgi-compendium="http://www.springframework.org/schema/osgi-compendium" xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:tool="http://www.springframework.org/schema/tool" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:webflow-config="http://www.springframework.org/schema/webflow-config">
+ <xsl:output method="text" />
+
+ <xsl:template match="/">
+
+ <!-- Match all attributes that holds a class or a comma delimited
+ list of classes and print them -->
+
+ <xsl:for-each select="
+ //beans:bean/@class
+ | //beans:*/@value-type
+ | //aop:*/@implement-interface
+ | //aop:*/@default-impl
+ | //context:load-time-weaver/@weaver-class
+ | //jee:jndi-lookup/@expected-type
+ | //jee:jndi-lookup/@proxy-interface
+ | //jee:remote-slsb/@ejbType
+ | //jee:*/@business-interface
+ | //lang:*/@script-interfaces
+ | //osgi:*/@interface
+ | //util:list/@list-class
+ | //util:set/@set-class
+ | //util:map/@map-class
+ | //webflow-config:*/@class
+ ">
+ <xsl:value-of select="." />
+ <xsl:text>
+ </xsl:text>
+ </xsl:for-each>
+
+ <!-- This seems some magic to get extra imports? -->
+
+ <xsl:for-each select="//beans:bean[@class='org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean'
+ or @class='org.springframework.osgi.service.importer.support.OsgiServiceProxyFactoryBean']">
+ <xsl:for-each select="beans:property[@name='interfaces']">
+ <xsl:value-of select="@value" />
+ <xsl:text>
+ </xsl:text>
+ </xsl:for-each>
+ </xsl:for-each>
+
+ </xsl:template>
+
+
+</xsl:stylesheet>
+
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/hibernate.xsl b/bundleplugin/src/main/java/aQute/lib/spring/hibernate.xsl
new file mode 100644
index 0000000..1f0a1c6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/hibernate.xsl
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:osgi-compendium="http://www.springframework.org/schema/osgi-compendium" xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:tool="http://www.springframework.org/schema/tool" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:webflow-config="http://www.springframework.org/schema/webflow-config">
+ <xsl:output method="text" />
+
+ <xsl:template match="/">
+
+ <!-- Match all attributes that holds a class or a comma delimited
+ list of classes and print them -->
+
+ <xsl:for-each select="//provider|//class">
+ <xsl:value-of select="." />
+ <xsl:text>
+ </xsl:text>
+ </xsl:for-each>
+ </xsl:template>
+
+
+</xsl:stylesheet>
+
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/jpa.xsl b/bundleplugin/src/main/java/aQute/lib/spring/jpa.xsl
new file mode 100644
index 0000000..43bba60
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/spring/jpa.xsl
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:osgi-compendium="http://www.springframework.org/schema/osgi-compendium" xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:tool="http://www.springframework.org/schema/tool" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:webflow-config="http://www.springframework.org/schema/webflow-config">
+ <xsl:output method="text" />
+
+ <xsl:template match="/">
+
+ <!-- Match all attributes that holds a class or a comma delimited
+ list of classes and print them -->
+
+ <xsl:for-each select="
+ //class/@name
+ | //composite-id/@class
+ | //component/@class
+ | //discriminator/@type
+ | //dynamic-component/@class
+ | //generator/@class
+ | //id/@type
+ | //import/@class
+ | //joined-subclass/@name
+ | //many-to-many/@class
+ | //many-to-one/@class
+ | //one-to-many/@class
+ | //one-to-one/@class
+ | //property/@type
+ | //subclass/@name
+ | //union-subclass/@name
+ | //version/@type
+ ">
+ <xsl:value-of select="." />
+ <xsl:text>
+ </xsl:text>
+ </xsl:for-each>
+ </xsl:template>
+
+
+</xsl:stylesheet>
+
diff --git a/bundleplugin/src/main/java/aQute/lib/tag/Tag.java b/bundleplugin/src/main/java/aQute/lib/tag/Tag.java
new file mode 100755
index 0000000..5ec25a0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/tag/Tag.java
@@ -0,0 +1,471 @@
+package aQute.lib.tag;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+
+/**
+ * The Tag class represents a minimal XML tree. It consist of a named element
+ * with a hashtable of named attributes. Methods are provided to walk the tree
+ * and get its constituents. The content of a Tag is a list that contains String
+ * objects or other Tag objects.
+ */
+public class Tag {
+ Tag parent; // Parent
+ String name; // Name
+ final Map<String, String> attributes = new LinkedHashMap<String, String>();
+ final List<Object> content = new ArrayList<Object>(); // Content
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
+ boolean cdata;
+
+ /**
+ * Construct a new Tag with a name.
+ */
+ public Tag(String name, Object... contents) {
+ this.name = name;
+ for (Object c : contents)
+ content.add(c);
+ }
+
+ public Tag(Tag parent, String name, Object... contents) {
+ this(name, contents);
+ parent.addContent(this);
+ }
+
+ /**
+ * Construct a new Tag with a name.
+ */
+ public Tag(String name, Map<String, String> attributes, Object... contents) {
+ this(name, contents);
+ this.attributes.putAll(attributes);
+
+ }
+
+ public Tag(String name, Map<String, String> attributes) {
+ this(name, attributes, new Object[0]);
+ }
+
+ /**
+ * Construct a new Tag with a name and a set of attributes. The attributes
+ * are given as ( name, value ) ...
+ */
+ public Tag(String name, String[] attributes, Object... contents) {
+ this(name, contents);
+ for (int i = 0; i < attributes.length; i += 2)
+ addAttribute(attributes[i], attributes[i + 1]);
+ }
+
+ public Tag(String name, String[] attributes) {
+ this(name, attributes, new Object[0]);
+ }
+
+ /**
+ * Add a new attribute.
+ */
+ public Tag addAttribute(String key, String value) {
+ if (value != null)
+ attributes.put(key, value);
+ return this;
+ }
+
+ /**
+ * Add a new attribute.
+ */
+ public Tag addAttribute(String key, Object value) {
+ if (value == null)
+ return this;
+ attributes.put(key, value.toString());
+ return this;
+ }
+
+ /**
+ * Add a new attribute.
+ */
+ public Tag addAttribute(String key, int value) {
+ attributes.put(key, Integer.toString(value));
+ return this;
+ }
+
+ /**
+ * Add a new date attribute. The date is formatted as the SimpleDateFormat
+ * describes at the top of this class.
+ */
+ public Tag addAttribute(String key, Date value) {
+ if (value != null)
+ attributes.put(key, format.format(value));
+ return this;
+ }
+
+ /**
+ * Add a new content string.
+ */
+ public Tag addContent(String string) {
+ if (string != null)
+ content.add(string);
+ return this;
+ }
+
+ /**
+ * Add a new content tag.
+ */
+ public Tag addContent(Tag tag) {
+ content.add(tag);
+ tag.parent = this;
+ return this;
+ }
+
+ /**
+ * Return the name of the tag.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the attribute value.
+ */
+ public String getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+ /**
+ * Return the attribute value or a default if not defined.
+ */
+ public String getAttribute(String key, String deflt) {
+ String answer = getAttribute(key);
+ return answer == null ? deflt : answer;
+ }
+
+ /**
+ * Answer the attributes as a Dictionary object.
+ */
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Return the contents.
+ */
+ public List<Object> getContents() {
+ return content;
+ }
+
+ /**
+ * Return a string representation of this Tag and all its children
+ * recursively.
+ */
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ print(0, new PrintWriter(sw));
+ return sw.toString();
+ }
+
+ /**
+ * Return only the tags of the first level of descendants that match the
+ * name.
+ */
+ public List<Object> getContents(String tag) {
+ List<Object> out = new ArrayList<Object>();
+ for (Object o : out) {
+ if (o instanceof Tag && ((Tag) o).getName().equals(tag))
+ out.add(o);
+ }
+ return out;
+ }
+
+ /**
+ * Return the whole contents as a String (no tag info and attributes).
+ */
+ public String getContentsAsString() {
+ StringBuilder sb = new StringBuilder();
+ getContentsAsString(sb);
+ return sb.toString();
+ }
+
+ /**
+ * convenient method to get the contents in a StringBuilder.
+ */
+ public void getContentsAsString(StringBuilder sb) {
+ for (Object o : content) {
+ if (o instanceof Tag)
+ ((Tag) o).getContentsAsString(sb);
+ else
+ sb.append(o.toString());
+ }
+ }
+
+ /**
+ * Print the tag formatted to a PrintWriter.
+ */
+ public Tag print(int indent, PrintWriter pw) {
+ pw.print("\n");
+ spaces(pw, indent);
+ pw.print('<');
+ pw.print(name);
+
+ for (String key : attributes.keySet()) {
+ String value = escape(attributes.get(key));
+ pw.print(' ');
+ pw.print(key);
+ pw.print("=\"");
+ pw.print(value);
+ pw.print("\"");
+ }
+
+ if (content.size() == 0)
+ pw.print('/');
+ else {
+ pw.print('>');
+ for (Object c : content) {
+ if (c instanceof String) {
+ if (cdata) {
+ pw.print("<![CDATA[");
+ String s = (String) c;
+ s = s.replaceAll("]]>", "] ]>");
+ pw.print(s);
+ pw.print("]]>");
+ } else
+ formatted(pw, indent + 2, 60, escape((String) c));
+ } else if (c instanceof Tag) {
+ Tag tag = (Tag) c;
+ tag.print(indent + 2, pw);
+ }
+ }
+ pw.print("\n");
+ spaces(pw, indent);
+ pw.print("</");
+ pw.print(name);
+ }
+ pw.print('>');
+ return this;
+ }
+
+ /**
+ * Convenience method to print a string nicely and does character conversion
+ * to entities.
+ */
+ void formatted(PrintWriter pw, int left, int width, String s) {
+ int pos = width + 1;
+ s = s.trim();
+
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (i == 0 || (Character.isWhitespace(c) && pos > width - 3)) {
+ pw.print("\n");
+ spaces(pw, left);
+ pos = 0;
+ }
+ switch (c) {
+ case '<':
+ pw.print("<");
+ pos += 4;
+ break;
+ case '>':
+ pw.print(">");
+ pos += 4;
+ break;
+ case '&':
+ pw.print("&");
+ pos += 5;
+ break;
+ default:
+ pw.print(c);
+ pos++;
+ break;
+ }
+
+ }
+ }
+
+ /**
+ * Escape a string, do entity conversion.
+ */
+ String escape(String s) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '<':
+ sb.append("<");
+ break;
+ case '>':
+ sb.append(">");
+ break;
+ case '\"':
+ sb.append(""");
+ break;
+ case '&':
+ sb.append("&");
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Make spaces.
+ */
+ void spaces(PrintWriter pw, int n) {
+ while (n-- > 0)
+ pw.print(' ');
+ }
+
+ /**
+ * root/preferences/native/os
+ */
+ public Collection<Tag> select(String path) {
+ return select(path, (Tag) null);
+ }
+
+ public Collection<Tag> select(String path, Tag mapping) {
+ List<Tag> v = new ArrayList<Tag>();
+ select(path, v, mapping);
+ return v;
+ }
+
+ void select(String path, List<Tag> results, Tag mapping) {
+ if (path.startsWith("//")) {
+ int i = path.indexOf('/', 2);
+ String name = path.substring(2, i < 0 ? path.length() : i);
+
+ for (Object o : content) {
+ if (o instanceof Tag) {
+ Tag child = (Tag) o;
+ if (match(name, child, mapping))
+ results.add(child);
+ child.select(path, results, mapping);
+ }
+
+ }
+ return;
+ }
+
+ if (path.length() == 0) {
+ results.add(this);
+ return;
+ }
+
+ int i = path.indexOf("/");
+ String elementName = path;
+ String remainder = "";
+ if (i > 0) {
+ elementName = path.substring(0, i);
+ remainder = path.substring(i + 1);
+ }
+
+ for (Object o : content) {
+ if (o instanceof Tag) {
+ Tag child = (Tag) o;
+ if (child.getName().equals(elementName) || elementName.equals("*"))
+ child.select(remainder, results, mapping);
+ }
+ }
+ }
+
+ public boolean match(String search, Tag child, Tag mapping) {
+ String target = child.getName();
+ String sn = null;
+ String tn = null;
+
+ if (search.equals("*"))
+ return true;
+
+ int s = search.indexOf(':');
+ if (s > 0) {
+ sn = search.substring(0, s);
+ search = search.substring(s + 1);
+ }
+ int t = target.indexOf(':');
+ if (t > 0) {
+ tn = target.substring(0, t);
+ target = target.substring(t + 1);
+ }
+
+ if (!search.equals(target)) // different tag names
+ return false;
+
+ if (mapping == null) {
+ return tn == sn || (sn != null && sn.equals(tn));
+ }
+ String suri = sn == null ? mapping.getAttribute("xmlns") : mapping
+ .getAttribute("xmlns:" + sn);
+ String turi = tn == null ? child.findRecursiveAttribute("xmlns") : child
+ .findRecursiveAttribute("xmlns:" + tn);
+ return turi == suri || (turi != null && suri != null && turi.equals(suri));
+ }
+
+ public String getString(String path) {
+ String attribute = null;
+ int index = path.indexOf("@");
+ if (index >= 0) {
+ // attribute
+ attribute = path.substring(index + 1);
+
+ if (index > 0) {
+ // prefix path
+ path = path.substring(index - 1); // skip -1
+ } else
+ path = "";
+ }
+ Collection<Tag> tags = select(path);
+ StringBuilder sb = new StringBuilder();
+ for (Tag tag : tags) {
+ if (attribute == null)
+ tag.getContentsAsString(sb);
+ else
+ sb.append(tag.getAttribute(attribute));
+ }
+ return sb.toString();
+ }
+
+ public String getStringContent() {
+ StringBuilder sb = new StringBuilder();
+ for (Object c : content) {
+ if (!(c instanceof Tag))
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ public String getNameSpace() {
+ return getNameSpace(name);
+ }
+
+ public String getNameSpace(String name) {
+ int index = name.indexOf(':');
+ if (index > 0) {
+ String ns = name.substring(0, index);
+ return findRecursiveAttribute("xmlns:" + ns);
+ }
+ return findRecursiveAttribute("xmlns");
+ }
+
+ public String findRecursiveAttribute(String name) {
+ String value = getAttribute(name);
+ if (value != null)
+ return value;
+ if (parent != null)
+ return parent.findRecursiveAttribute(name);
+ return null;
+ }
+
+ public String getLocalName() {
+ int index = name.indexOf(':');
+ if (index <= 0)
+ return name;
+
+ return name.substring(index + 1);
+ }
+
+ public void rename(String string) {
+ name = string;
+ }
+
+ public void setCDATA() {
+ cdata = true;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/tag/packageinfo b/bundleplugin/src/main/java/aQute/lib/tag/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/tag/packageinfo
@@ -0,0 +1 @@
+version 1.0