Apply latest bnd code

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1349301 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java b/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
index e451eb0..90a20af 100644
--- a/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
+++ b/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
@@ -14,7 +14,7 @@
 
 	public boolean analyzeJar(Analyzer analyzer) throws Exception {
 		Parameters header = OSGiHeader.parseHeader(analyzer
-				.getProperty("-dsannotations"));
+				.getProperty(Constants.DSANNOTATIONS));
 		if ( header.size()==0)
 			return false;
 		
diff --git a/bundleplugin/src/main/java/aQute/lib/converter/Converter.java b/bundleplugin/src/main/java/aQute/lib/converter/Converter.java
index abbbf9b..6b5af19 100644
--- a/bundleplugin/src/main/java/aQute/lib/converter/Converter.java
+++ b/bundleplugin/src/main/java/aQute/lib/converter/Converter.java
@@ -14,10 +14,19 @@
  * @author aqute
  * 
  */
-@SuppressWarnings({ "unchecked", "rawtypes" }) public class Converter {
-	boolean	fatal	= true;
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class Converter {
+	public interface Hook {
+		Object convert(Type dest, Object o) throws Exception;
+	}
+
+	boolean			fatal	= true;
+	Map<Type, Hook>	hooks = new HashMap<Type, Converter.Hook>();
 
 	public <T> T convert(Class<T> type, Object o) throws Exception {
+		// Is it a compatible type?
+		if (type.isAssignableFrom(o.getClass()))
+			return (T) o;
 		return (T) convert((Type) type, o);
 	}
 
@@ -25,11 +34,16 @@
 		if (o == null)
 			return null; // compatible with any
 
+		
+		Hook hook = hooks.get(type);
+		if ( hook != null ) {
+			Object value = hook.convert(type, o);
+			if ( value != null)
+				return value;
+		}
+		
 		Class resultType = getRawClass(type);
-		Class<?> actualType = o.getClass();
-		// Is it a compatible type?
-		if (resultType.isAssignableFrom(actualType))
-			return o;
+		Class< ? > actualType = o.getClass();
 
 		// We can always make a string
 
@@ -80,7 +94,8 @@
 					if (m.getReturnType() == byte[].class)
 						return m.invoke(o);
 
-				} catch (Exception e) {
+				}
+				catch (Exception e) {
 					// Ignore
 				}
 			}
@@ -88,6 +103,9 @@
 			return array(resultType.getComponentType(), o);
 		}
 
+		if (resultType.isAssignableFrom(o.getClass()))
+			return o;
+
 		// Simple type coercion
 
 		if (resultType == boolean.class || resultType == Boolean.class) {
@@ -98,47 +116,61 @@
 				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;
 		}
+		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();
 
@@ -158,15 +190,17 @@
 			}
 
 			try {
-				Constructor<?> c = resultType.getConstructor(String.class);
+				Constructor< ? > c = resultType.getConstructor(String.class);
 				return c.newInstance(o.toString());
-			} catch (Throwable t) {
+			}
+			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) {
+			}
+			catch (Throwable t) {
 			}
 
 			if (resultType == Character.class && input.length() == 1)
@@ -181,11 +215,42 @@
 					int nn = n.intValue();
 					if (nn > 0 && nn < vs.length)
 						return vs[nn];
-				} catch (Exception e) {
+				}
+				catch (Exception e) {
 					// Ignore
 				}
 			}
 		}
+
+		// Translate arrays with length 1 by picking the single element
+		if (actualType.isArray() && Array.getLength(o) == 1) {
+			return convert(type, Array.get(o, 0));
+		}
+
+		// Translate collections with size 1 by picking the single element
+		if (o instanceof Collection) {
+			Collection col = (Collection) o;
+			if (col.size() == 1)
+				return convert(type, col.iterator().next());
+		}
+
+		if (o instanceof Map) {
+			try {
+				Map<Object, Object> map = (Map) o;
+				Object instance = resultType.newInstance();
+				for (Map.Entry e : map.entrySet()) {
+					String key = (String) e.getKey();
+					Field f = resultType.getField(key);
+					Object value = convert(f.getGenericType(), e.getValue());
+					f.set(instance, value);
+				}
+				return instance;
+			}
+			catch (Exception e) {
+				// fall through
+			}
+		}
+
 		return error("No conversion found for " + o.getClass() + " to " + type);
 	}
 
@@ -203,35 +268,43 @@
 			String s = (String) o;
 			try {
 				return Double.parseDouble(s);
-			} catch (Exception e) {
+			}
+			catch (Exception e) {
 				// Ignore
 			}
 		}
 		return null;
 	}
 
-	private Collection collection(Type collectionType, Class<? extends Collection> rawClass,
+	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
+				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;
@@ -248,21 +321,25 @@
 		return collection;
 	}
 
-	private Map map(Type mapType, Class<? extends Map<?, ?>> rawClass, Object o) throws Exception {
+	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
+				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);
+		Map< ? , ? > input = toMap(o);
 
 		Type keyType = Object.class;
 		Type valueType = Object.class;
@@ -272,20 +349,21 @@
 			valueType = ptype.getActualTypeArguments()[1];
 		}
 
-		for (Map.Entry<?, ?> entry : input.entrySet()) {
+		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);
+			if (key == null)
+				error("Key for map must not be null: " + input);
+			else
+				result.put(key, value);
 		}
 
 		return result;
 	}
 
 	public Object array(Type type, Object o) throws Exception {
-		Collection<?> input = toCollection(o);
-		Class<?> componentClass = getRawClass(type);
+		Collection< ? > input = toCollection(o);
+		Class< ? > componentClass = getRawClass(type);
 		Object array = Array.newInstance(componentClass, input.size());
 
 		int i = 0;
@@ -295,12 +373,12 @@
 		return array;
 	}
 
-	private Class<?> getRawClass(Type type) {
+	private Class< ? > getRawClass(Type type) {
 		if (type instanceof Class)
-			return (Class<?>) type;
+			return (Class< ? >) type;
 
 		if (type instanceof ParameterizedType)
-			return (Class<?>) ((ParameterizedType) type).getRawType();
+			return (Class< ? >) ((ParameterizedType) type).getRawType();
 
 		if (type instanceof GenericArrayType) {
 			Type componentType = ((GenericArrayType) type).getGenericComponentType();
@@ -320,9 +398,9 @@
 		return Object.class;
 	}
 
-	public Collection<?> toCollection(Object o) {
+	public Collection< ? > toCollection(Object o) {
 		if (o instanceof Collection)
-			return (Collection<?>) o;
+			return (Collection< ? >) o;
 
 		if (o.getClass().isArray()) {
 			if (o.getClass().getComponentType().isPrimitive()) {
@@ -339,9 +417,9 @@
 		return Arrays.asList(o);
 	}
 
-	public Map<?, ?> toMap(Object o) throws Exception {
+	public Map< ? , ? > toMap(Object o) throws Exception {
 		if (o instanceof Map)
-			return (Map<?, ?>) o;
+			return (Map< ? , ? >) o;
 		Map result = new HashMap();
 		Field fields[] = o.getClass().getFields();
 		for (Field f : fields)
@@ -361,4 +439,10 @@
 	public void setFatalIsException(boolean b) {
 		fatal = b;
 	}
+	
+	
+	public Converter hook(Type type, Hook hook) {
+		this.hooks.put(type, hook);
+		return this;
+	}
 }
diff --git a/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java b/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java
index 3fb14ee..89492c1 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java
@@ -37,7 +37,7 @@
 
 	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
 			throws IOException, Exception {
-		Collection<?> collection = (Collection<?>) object;
+		Iterable<?> collection = (Iterable<?>) object;
 
 		app.append("[");
 		String del = "";
diff --git a/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
index a45b5b4..fa5c751 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
@@ -53,6 +53,9 @@
 	private static FileHandler						fh					= new FileHandler();
 	private static ByteArrayHandler					byteh				= new ByteArrayHandler();
 
+	boolean ignorenull;
+	
+
 	/**
 	 * Create a new Encoder with the state and appropriate API.
 	 * 
@@ -147,7 +150,7 @@
 
 			if (Enum.class.isAssignableFrom(clazz))
 				h = new EnumHandler(clazz);
-			else if (Collection.class.isAssignableFrom(clazz)) // A Non Generic
+			else if (Iterable.class.isAssignableFrom(clazz)) // A Non Generic
 																// collection
 
 				h = dch;
@@ -188,7 +191,7 @@
 				Type rawType = pt.getRawType();
 				if (rawType instanceof Class) {
 					Class<?> rawClass = (Class<?>) rawType;
-					if (Collection.class.isAssignableFrom(rawClass))
+					if (Iterable.class.isAssignableFrom(rawClass))
 						h = new CollectionHandler(rawClass, pt.getActualTypeArguments()[0]);
 					else if (Map.class.isAssignableFrom(rawClass))
 						h = new MapHandler(rawClass, pt.getActualTypeArguments()[0],
@@ -478,4 +481,18 @@
 						+ type);
 	}
 
+	/**
+	 * Ignore null values in output and input
+	 * @param ignorenull
+	 * @return
+	 */
+	public JSONCodec setIgnorenull(boolean ignorenull) {
+		this.ignorenull = ignorenull;
+		return this;
+	}
+
+	public boolean isIgnorenull() {
+		return ignorenull;
+	}
+
 }
\ 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
index 5a413b6..9299878 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java
@@ -68,7 +68,8 @@
 
 			c = r.next();
 			Object value = r.codec.decode(valueType, r);
-			map.put(key, value);
+			if ( value != null || !r.codec.ignorenull)
+				map.put(key, value);
 
 			c = r.skipWs();
 			
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java
index fc6f6bd..2a876a4 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java
@@ -95,7 +95,8 @@
 			if (f != null) {
 				// We have a field and thus a type
 				Object value = r.codec.decode(f.getGenericType(), r);
-				f.set(targetObject, value);
+				if ( value != null || !r.codec.ignorenull)
+					f.set(targetObject, value);
 			} else {
 				// No field, but may extra is defined
 				if (extra == null) {
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java b/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java
index b196a1c..8df703f 100644
--- a/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java
@@ -62,7 +62,7 @@
 			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															};
+			SIGNATURE_TEST, REQUIRE_CAPABILITY, PROVIDE_CAPABILITY					};
 
 	String								BUILDPATH									= "-buildpath";
 	String								BUILDPACKAGES								= "-buildpackages";
@@ -73,7 +73,8 @@
 	String								DEPENDSON									= "-dependson";
 	String								DEPLOY										= "-deploy";
 	String								DEPLOYREPO									= "-deployrepo";
-	String								DIGESTS									= "-digests";
+	String								DIGESTS										= "-digests";
+	String								DSANNOTATIONS								= "-dsannotations";
 	String								DONOTCOPY									= "-donotcopy";
 	String								DEBUG										= "-debug";
 	String								EXPORT_CONTENTS								= "-exportcontents";
@@ -89,7 +90,8 @@
 	String								NOEXTRAHEADERS								= "-noextraheaders";
 	String								NOMANIFEST									= "-nomanifest";
 	String								NOUSES										= "-nouses";
-	@Deprecated String					NOPE										= "-nope";
+	@Deprecated
+	String								NOPE										= "-nope";
 	String								NOBUNDLES									= "-nobundles";
 	String								PEDANTIC									= "-pedantic";
 	String								PLUGIN										= "-plugin";
@@ -124,11 +126,14 @@
 	String								TESTCONTINUOUS								= "-testcontinuous";
 	String								UNDERTEST									= "-undertest";
 	String								VERBOSE										= "-verbose";
-	@Deprecated String					VERSIONPOLICY_IMPL							= "-versionpolicy-impl";
-	@Deprecated String					VERSIONPOLICY_USES							= "-versionpolicy-uses";
+	@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";
+	@Deprecated
+	String								VERSIONPOLICY								= "-versionpolicy";
 	String								WAB											= "-wab";
 	String								WABLIB										= "-wablib";
 	String								REQUIRE_BND									= "-require-bnd";
@@ -137,14 +142,14 @@
 	String								CLASSPATH									= "-classpath";
 	String								OUTPUT										= "-output";
 
-	String								options[]									= { BUILDPATH,
+	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										};
+			RUNTRACE, TESTCONTINUOUS, SNAPSHOT, NAMESECTION, DIGESTS, DSANNOTATIONS				};
 
 	// Ignore bundle specific headers. These bundles do not make
 	// a lot of sense to inherit
@@ -152,7 +157,7 @@
 			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											};
+			SIGNATURE_TEST, WAB, WABLIB, REQUIRE_CAPABILITY, PROVIDE_CAPABILITY, DSANNOTATIONS, SERVICE_COMPONENT		};
 
 	char								DUPLICATE_MARKER							= '~';
 	String								SPECIFICATION_VERSION						= "specification-version";
@@ -223,7 +228,7 @@
 	String								DEFAULT_JAR_EXTENSION						= ".jar";
 	String								DEFAULT_BAR_EXTENSION						= ".bar";
 	String								DEFAULT_BNDRUN_EXTENSION					= ".bndrun";
-	String[]							METAPACKAGES								= { "META-INF",
+	String[]							METAPACKAGES								= {"META-INF",
 			"OSGI-INF", "OSGI-OPT"													};
 
 	String								CURRENT_VERSION								= "@";
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java b/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
index 4b81607..9263e3c 100755
--- a/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
@@ -4,24 +4,27 @@
 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) {
+		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) {
+
+		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;
@@ -30,7 +33,7 @@
 			if (pathname.isDirectory() && isRecursive()) {
 				return true;
 			}
-			
+
 			if (instruction == null) {
 				return true;
 			}
@@ -47,87 +50,89 @@
 	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
+		}
+		else
 			negated = false;
 
-		if ( input.equals("*")) {
+		if (input.equals("*")) {
 			any = true;
-			literal= false;
-			match= null;
+			literal = false;
+			match = null;
 			return;
 		}
-		
+
 		any = false;
 		if (s.startsWith("=")) {
 			match = s.substring(1);
 			literal = true;
-		} else  {
+		}
+		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;
+					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 ) {
+
+			if (!wildcards) {
 				literal = true;
 				match = s;
-			} else {
+			}
+			else {
 				literal = false;
 				match = sb.toString();
 			}
 		}
 
-		
 	}
 
 	public boolean matches(String value) {
 		if (any)
 			return true;
-		
-		if (literal )
+
+		if (literal)
 			return match.equals(value);
 		else
 			return getMatcher(value).matches();
@@ -164,7 +169,6 @@
 		return optional;
 	}
 
-
 	public boolean isLiteral() {
 		return literal;
 	}
diff --git a/bundleplugin/src/main/java/aQute/libg/command/Command.java b/bundleplugin/src/main/java/aQute/libg/command/Command.java
index 0a79a58..209be52 100644
--- a/bundleplugin/src/main/java/aQute/libg/command/Command.java
+++ b/bundleplugin/src/main/java/aQute/libg/command/Command.java
@@ -37,7 +37,6 @@
 	}
 
 	public int execute(InputStream in, Appendable stdout, Appendable stderr) throws Exception {
-		int result;
 		if (reporter != null) {
 			reporter.trace("executing cmd: %s", arguments);
 		}
@@ -65,15 +64,12 @@
 		Runtime.getRuntime().addShutdownHook(hook);
 		TimerTask timer = null;
 		OutputStream stdin = process.getOutputStream();
-		final InputStreamHandler handler = in != null ? new InputStreamHandler(in, stdin) : null;
 
 		if (timeout != 0) {
 			timer = new TimerTask() {
 				public void run() {
 					timedout = true;
 					process.destroy();
-					if (handler != null)
-						handler.interrupt();
 				}
 			};
 			Command.timer.schedule(timer, timeout);
@@ -83,15 +79,36 @@
 		try {
 			InputStream err = process.getErrorStream();
 			try {
-				new Collector(out, stdout).start();
-				new Collector(err, stderr).start();
-				if (handler != null)
-					handler.start();
-
-				result = process.waitFor();
+				Collector cout = new Collector(out, stdout);
+				cout.start();
+				Collector cerr = new Collector(err, stderr);
+				cerr.start();
+	
+				try {
+					int c = in.read();
+					while (c >= 0) {
+						stdin.write(c);
+						if ( c == '\n')
+							stdin.flush();
+						c = in.read();
+					}
+				}
+				catch (InterruptedIOException e) {
+					// Ignore here
+				}
+				catch (Exception e) {
+					// Who cares?
+				} finally {
+					stdin.close();
+				}
+				
 				if (reporter != null)
-					reporter.trace("exited process.waitFor, %s", result);
-
+					reporter.trace("exited process");
+				
+				cerr.join();
+				cout.join();
+				if (reporter != null)
+					reporter.trace("stdout/stderr streams have finished");
 			}
 			finally {
 				err.close();
@@ -102,16 +119,16 @@
 			if (timer != null)
 				timer.cancel();
 			Runtime.getRuntime().removeShutdownHook(hook);
-			if (handler != null)
-				handler.interrupt();
 		}
+		
+		byte exitValue = (byte) process.waitFor();
 		if (reporter != null)
-			reporter.trace("cmd %s executed with result=%d, result: %s/%s", arguments, result,
-					stdout, stderr);
+			reporter.trace("cmd %s executed with result=%d, result: %s/%s, timedout=%s", arguments, exitValue,
+					stdout, stderr, timedout);
 
 		if (timedout)
 			return Integer.MIN_VALUE;
-		byte exitValue = (byte) process.exitValue();
+		
 		return exitValue;
 	}
 
@@ -183,42 +200,6 @@
 		}
 	}
 
-	static class InputStreamHandler extends Thread {
-		final InputStream	in;
-		final OutputStream	stdin;
-
-		InputStreamHandler(InputStream in, OutputStream stdin) {
-			this.stdin = stdin;
-			this.in = in;
-			setDaemon(true);
-		}
-
-		public void run() {
-			try {
-				int c = in.read();
-				while (c >= 0) {
-					stdin.write(c);
-					stdin.flush();
-					c = in.read();
-				}
-			}
-			catch (InterruptedIOException e) {
-				// Ignore here
-			}
-			catch (Exception e) {
-				// Who cares?
-			}
-			finally {
-				try {
-					stdin.close();
-				}
-				catch (IOException e) {
-					// Who cares?
-				}
-			}
-		}
-	}
-
 	public Command var(String name, String value) {
 		variables.put(name, value);
 		return this;
diff --git a/bundleplugin/src/main/java/aQute/libg/command/packageinfo b/bundleplugin/src/main/java/aQute/libg/command/packageinfo
index a2afe57..63eb236 100644
--- a/bundleplugin/src/main/java/aQute/libg/command/packageinfo
+++ b/bundleplugin/src/main/java/aQute/libg/command/packageinfo
@@ -1 +1 @@
-version 2.1.0
+version 3.0.0