Sync bndlib code

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1381708 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/Container.java b/bundleplugin/src/main/java/aQute/bnd/build/Container.java
index 0de335d..f693ddb 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/Container.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Container.java
@@ -5,46 +5,54 @@
 import java.util.jar.*;
 
 import aQute.bnd.osgi.*;
-import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.bnd.service.*;
 
 public class Container {
 	public enum TYPE {
 		REPO, PROJECT, EXTERNAL, LIBRARY, ERROR
 	}
 
-	final File					file;
+	private final File			file;
+	private final String		path;
 	final TYPE					type;
 	final String				bsn;
 	final String				version;
 	final String				error;
 	final Project				project;
+	final DownloadBlocker		db;
 	volatile Map<String,String>	attributes;
 	private long				manifestTime;
 	private Manifest			manifest;
 
 	Container(Project project, String bsn, String version, TYPE type, File source, String error,
-			Map<String,String> attributes) {
+			Map<String,String> attributes, DownloadBlocker db) {
 		this.bsn = bsn;
 		this.version = version;
 		this.type = type;
 		this.file = source != null ? source : new File("/" + bsn + ":" + version + ":" + type);
+		this.path = file.getAbsolutePath();
+		
 		this.project = project;
 		this.error = error;
 		if (attributes == null || attributes.isEmpty())
 			this.attributes = Collections.emptyMap();
 		else
 			this.attributes = attributes;
+		this.db = db;
 	}
 
 	public Container(Project project, File file) {
-		this(project, file.getName(), "project", TYPE.PROJECT, file, null, null);
+		this(project, file.getName(), "project", TYPE.PROJECT, file, null, null, null);
 	}
 
-	public Container(File file) {
-		this(null, file.getName(), "project", TYPE.EXTERNAL, file, null, null);
+	public Container(File file, DownloadBlocker db) {
+		this(null, file.getName(), "project", TYPE.EXTERNAL, file, null, null, db);
 	}
 
 	public File getFile() {
+		if (db != null && db.getReason() != null) {
+			return new File(db.getReason() + ": " + file);
+		}
 		return file;
 	}
 
@@ -59,7 +67,7 @@
 		switch (type) {
 			case EXTERNAL :
 			case REPO :
-				files.add(file);
+				files.add(getFile());
 				return true;
 
 			case PROJECT :
@@ -106,13 +114,13 @@
 	@Override
 	public boolean equals(Object other) {
 		if (other instanceof Container)
-			return file.equals(((Container) other).file);
+			return path.equals(((Container) other).path);
 		return false;
 	}
 
 	@Override
 	public int hashCode() {
-		return file.hashCode();
+		return path.hashCode();
 	}
 
 	public Project getProject() {
@@ -162,7 +170,7 @@
 			BufferedReader rd = null;
 			String line;
 			try {
-				in = new FileInputStream(file);
+				in = new FileInputStream(getFile());
 				rd = new BufferedReader(new InputStreamReader(in, Constants.DEFAULT_CHARSET));
 				while ((line = rd.readLine()) != null) {
 					line = line.trim();
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/DownloadBlocker.java b/bundleplugin/src/main/java/aQute/bnd/build/DownloadBlocker.java
new file mode 100644
index 0000000..5b9b785
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/build/DownloadBlocker.java
@@ -0,0 +1,111 @@
+package aQute.bnd.build;
+
+import java.io.*;
+
+import aQute.bnd.service.*;
+import aQute.service.reporter.*;
+
+/**
+ * This class is intended to be used by the users of a {@link RepositoryPlugin}.
+ * The
+ * {@link RepositoryPlugin#get(String, aQute.bnd.version.Version, java.util.Map, aQute.bnd.service.RepositoryPlugin.DownloadListener...)}
+ * method takes one or more Download Listeners. These are called back with the
+ * success or failure of a download. This class is a simple implementation of
+ * this model, just call {@link #getReason()} and it blocks until success or
+ * failure is called.
+ */
+public class DownloadBlocker implements RepositoryPlugin.DownloadListener {
+	public enum Stage {
+		INIT, SUCCESS, FAILURE
+	};
+
+	private volatile Stage	stage	= Stage.INIT;
+	private String			failure;
+	private File			file;
+	private final Reporter	reporter;
+
+	public DownloadBlocker(Reporter reporter) {
+		this.reporter = reporter;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see
+	 * aQute.bnd.service.RepositoryPlugin.DownloadListener#success(java.io.File)
+	 */
+	public void success(File file) throws Exception {
+		synchronized (this) {
+			assert stage == Stage.INIT;
+			stage = Stage.SUCCESS;
+			this.file = file;
+			notifyAll();
+		}
+		if (reporter != null)
+			reporter.trace("successfully downloaded %s", file);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see
+	 * aQute.bnd.service.RepositoryPlugin.DownloadListener#failure(java.io.File,
+	 * java.lang.String)
+	 */
+	public void failure(File file, String reason) throws Exception {
+		synchronized (this) {
+			assert stage == Stage.INIT;
+			stage = Stage.FAILURE;
+			this.failure = reason;
+			this.file = file;
+			notifyAll();
+		}
+		if (reporter != null)
+			reporter.error("Download %s %s", reason, file);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see
+	 * aQute.bnd.service.RepositoryPlugin.DownloadListener#progress(java.io.
+	 * File, int)
+	 */
+	public boolean progress(File file, int percentage) throws Exception {
+		assert stage == Stage.INIT;
+		return true;
+	}
+
+	/**
+	 * Return a failure reason or null. This method will block until either
+	 * {@link #success(File)} or {@link #failure(File, String)} has been called.
+	 * It can be called many times.
+	 * 
+	 * @return null or a reason for a failure
+	 */
+	public synchronized String getReason() {
+		try {
+			while (stage == Stage.INIT)
+				wait();
+		}
+		catch (InterruptedException e) {
+			return "Interrupted";
+		}
+
+		return failure;
+	}
+
+	/**
+	 * Return the stage we're in
+	 * 
+	 * @return the current stage
+	 */
+	public Stage getStage() {
+		return stage;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		return "DownloadBlocker(" + stage + "," + file + ", " + failure + ")";
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/Project.java b/bundleplugin/src/main/java/aQute/bnd/build/Project.java
index 9c5c2ec..b6377c6 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/Project.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Project.java
@@ -7,6 +7,7 @@
 import java.util.Map.Entry;
 import java.util.concurrent.locks.*;
 import java.util.jar.*;
+import java.util.regex.*;
 
 import aQute.bnd.header.*;
 import aQute.bnd.help.*;
@@ -15,7 +16,6 @@
 import aQute.bnd.osgi.eclipse.*;
 import aQute.bnd.service.*;
 import aQute.bnd.service.RepositoryPlugin.PutResult;
-import aQute.bnd.service.RepositoryPlugin.Strategy;
 import aQute.bnd.service.action.*;
 import aQute.bnd.version.*;
 import aQute.lib.io.*;
@@ -29,6 +29,7 @@
 
 public class Project extends Processor {
 
+	final static Pattern		VERSION_ANNOTATION		= Pattern.compile("@\\s*(:?aQute\\.bnd\\.annotation\\.)?Version\\s*\\(\\s*(:?value\\s*=\\s*)?\"(\\d+(:?\\.\\d+(:?\\.\\d+(:?\\.[\\d\\w-_]+)?)?)?)\"\\s*\\)");
 	final static String			DEFAULT_ACTIONS			= "build; label='Build', test; label='Test', run; label='Run', clean; label='Clean', release; label='Release', refreshAll; label=Refresh, deploy;label=Deploy";
 	public final static String	BNDFILE					= "bnd.bnd";
 	public final static String	BNDCNF					= "cnf";
@@ -56,7 +57,7 @@
 	boolean						delayRunDependencies	= false;
 	final ProjectMessages		msgs					= ReporterMessages.base(this, ProjectMessages.class);
 
-	public Project(Workspace workspace, @SuppressWarnings("unused") File projectDir, File buildFile) throws Exception {
+	public Project(Workspace workspace, File projectDir, File buildFile) throws Exception {
 		super(workspace);
 		this.workspace = workspace;
 		setFileMustExist(false);
@@ -227,26 +228,27 @@
 					// We might have some other projects we want build
 					// before we do anything, but these projects are not in
 					// our path. The -dependson allows you to build them before.
+					// The values are possibly negated globbing patterns.
 
-					List<Project> dependencies = new ArrayList<Project>();
 					// dependencies.add( getWorkspace().getProject("cnf"));
 
 					String dp = getProperty(Constants.DEPENDSON);
-					Set<String> requiredProjectNames = new Parameters(dp).keySet();
+					Set<String> requiredProjectNames = new LinkedHashSet<String>(new Parameters(dp).keySet());
+					
+					//Allow DependencyConstributors to modify requiredProjectNames
 					List<DependencyContributor> dcs = getPlugins(DependencyContributor.class);
 					for (DependencyContributor dc : dcs)
 						dc.addDependencies(this, requiredProjectNames);
-
-					for (String p : requiredProjectNames) {
-						Project required = getWorkspace().getProject(p);
-						if (required == null)
-							msgs.MissingDependson_(p);
-						else {
-							dependencies.add(required);
-						}
-
-					}
-
+					
+					Instructions is = new Instructions(requiredProjectNames);
+					
+					Set<Instruction> unused = new HashSet<Instruction>();
+					Collection<Project> projects = getWorkspace().getAllProjects();
+					Collection<Project> dependencies = is.select(projects, unused, false);
+					
+					for (Instruction u: unused) 
+						msgs.MissingDependson_(u.getInput());							
+						
 					// We have two paths that consists of repo files, projects,
 					// or some other stuff. The doPath routine adds them to the
 					// path and extracts the projects so we can build them
@@ -367,7 +369,6 @@
 
 	private List<Container> parseBuildpath() throws Exception {
 		List<Container> bundles = getBundles(Strategy.LOWEST, getProperty(Constants.BUILDPATH), Constants.BUILDPATH);
-		appendPackages(Strategy.LOWEST, getProperty(Constants.BUILDPACKAGES), bundles, ResolverMode.build);
 		return bundles;
 	}
 
@@ -422,7 +423,7 @@
 						Project project = getWorkspace().getProject(bsn);
 						if (project != null && project.exists()) {
 							File f = project.getOutput();
-							found = new Container(project, bsn, versionRange, Container.TYPE.PROJECT, f, null, attrs);
+							found = new Container(project, bsn, versionRange, Container.TYPE.PROJECT, f, null, attrs, null);
 						} else {
 							msgs.NoSuchProject(bsn, spec);
 							continue;
@@ -433,9 +434,9 @@
 						if (!f.exists())
 							error = "File does not exist: " + f.getAbsolutePath();
 						if (f.getName().endsWith(".lib")) {
-							found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f, error, attrs);
+							found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f, error, attrs, null);
 						} else {
-							found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f, error, attrs);
+							found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f, error, attrs, null);
 						}
 					} else {
 						found = getBundle(bsn, versionRange, strategyx, attrs);
@@ -453,7 +454,7 @@
 				} else {
 					// Oops, not a bundle in sight :-(
 					Container x = new Container(this, bsn, versionRange, Container.TYPE.ERROR, null, bsn + ";version="
-							+ versionRange + " not found", attrs);
+							+ versionRange + " not found", attrs, null);
 					result.add(x);
 					warning("Can not find URL for bsn " + bsn);
 				}
@@ -480,72 +481,6 @@
 		return getBundles(strategy, spec, null);
 	}
 
-	/**
-	 * Calculates the containers required to fulfil the {@code -buildpackages}
-	 * instruction, and appends them to the existing list of containers.
-	 * 
-	 * @param strategyx
-	 *            The package-version disambiguation strategy.
-	 * @param spec
-	 *            The value of the @{code -buildpackages} instruction.
-	 * @throws Exception
-	 */
-	public void appendPackages(Strategy strategyx, String spec, List<Container> resolvedBundles, ResolverMode mode)
-			throws Exception {
-		Map<File,Container> pkgResolvedBundles = new HashMap<File,Container>();
-
-		List<Entry<String,Attrs>> queue = new LinkedList<Map.Entry<String,Attrs>>();
-		queue.addAll(new Parameters(spec).entrySet());
-
-		while (!queue.isEmpty()) {
-			Entry<String,Attrs> entry = queue.remove(0);
-
-			String pkgName = entry.getKey();
-			Map<String,String> attrs = entry.getValue();
-
-			Container found = null;
-
-			String versionRange = attrs.get(Constants.VERSION_ATTRIBUTE);
-			if ("latest".equals(versionRange) || "snapshot".equals(versionRange))
-				found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
-
-			if (found == null)
-				found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
-
-			if (found != null) {
-				if (resolvedBundles.contains(found)) {
-					// Don't add his bundle because it was already included
-					// using -buildpath
-				} else {
-					List<Container> libs = found.getMembers();
-					for (Container cc : libs) {
-						Container existing = pkgResolvedBundles.get(cc.file);
-						if (existing != null)
-							addToPackageList(existing, attrs.get("packages"));
-						else {
-							addToPackageList(cc, attrs.get("packages"));
-							pkgResolvedBundles.put(cc.file, cc);
-						}
-
-						String importUses = cc.getAttributes().get("import-uses");
-						if (importUses != null)
-							queue.addAll(0, new Parameters(importUses).entrySet());
-					}
-				}
-			} else {
-				// Unable to resolve
-				Container x = new Container(this, "X", versionRange, Container.TYPE.ERROR, null, "package " + pkgName
-						+ ";version=" + versionRange + " not found", attrs);
-				resolvedBundles.add(x);
-				warning("Can not find URL for package " + pkgName);
-			}
-		}
-
-		for (Container container : pkgResolvedBundles.values()) {
-			resolvedBundles.add(container);
-		}
-	}
-
 	static void mergeNames(String names, Set<String> set) {
 		StringTokenizer tokenizer = new StringTokenizer(names, ",");
 		while (tokenizer.hasMoreTokens())
@@ -577,68 +512,6 @@
 	}
 
 	/**
-	 * Find a container to fulfil a package requirement
-	 * 
-	 * @param packageName
-	 *            The package required
-	 * @param range
-	 *            The package version range required
-	 * @param strategyx
-	 *            The package-version disambiguation strategy
-	 * @param attrs
-	 *            Other attributes specified by the search.
-	 * @return
-	 * @throws Exception
-	 */
-	public Container getPackage(String packageName, String range, Strategy strategyx, Map<String,String> attrs,
-			ResolverMode mode) throws Exception {
-		if ("snapshot".equals(range))
-			return new Container(this, "", range, Container.TYPE.ERROR, null,
-					"snapshot not supported for package lookups", null);
-
-		if (attrs == null)
-			attrs = new HashMap<String,String>(2);
-		attrs.put("package", packageName);
-		attrs.put("mode", mode.name());
-
-		Strategy useStrategy = findStrategy(attrs, strategyx, range);
-
-		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
-		for (RepositoryPlugin plugin : plugins) {
-			try {
-				File result = plugin.get(null, range, useStrategy, attrs);
-				if (result != null) {
-					if (result.getName().endsWith("lib"))
-						return new Container(this, result.getName(), range, Container.TYPE.LIBRARY, result, null, attrs);
-					return new Container(this, result.getName(), range, Container.TYPE.REPO, result, null, attrs);
-				}
-			}
-			catch (Exception e) {
-				// Ignore... lots of repos will fail here
-			}
-		}
-
-		return new Container(this, "X", range, Container.TYPE.ERROR, null, "package " + packageName + ";version="
-				+ range + " Not found in " + plugins, null);
-	}
-
-	private Strategy findStrategy(Map<String,String> attrs, Strategy defaultStrategy, String versionRange) {
-		Strategy useStrategy = defaultStrategy;
-		String overrideStrategy = attrs.get("strategy");
-		if (overrideStrategy != null) {
-			if ("highest".equalsIgnoreCase(overrideStrategy))
-				useStrategy = Strategy.HIGHEST;
-			else if ("lowest".equalsIgnoreCase(overrideStrategy))
-				useStrategy = Strategy.LOWEST;
-			else if ("exact".equalsIgnoreCase(overrideStrategy))
-				useStrategy = Strategy.EXACT;
-		}
-		if ("latest".equals(versionRange))
-			useStrategy = Strategy.HIGHEST;
-		return useStrategy;
-	}
-
-	/**
 	 * The user selected pom in a path. This will place the pom as well as its
 	 * dependencies on the list
 	 * 
@@ -663,7 +536,7 @@
 			Set<Pom> dependencies = pom.getDependencies(act);
 			for (Pom sub : dependencies) {
 				File artifact = sub.getArtifact();
-				Container container = new Container(artifact);
+				Container container = new Container(artifact, null);
 				result.add(container);
 			}
 		}
@@ -753,10 +626,10 @@
 			dependson.add(required);
 		}
 		for (File f : eclipse.getClasspath()) {
-			buildpath.add(new Container(f));
+			buildpath.add(new Container(f, null));
 		}
 		for (File f : eclipse.getBootclasspath()) {
-			bootclasspath.add(new Container(f));
+			bootclasspath.add(new Container(f, null));
 		}
 		sourcepath.addAll(eclipse.getSourcepath());
 		allsourcepath.addAll(eclipse.getAllSources());
@@ -939,14 +812,18 @@
 		List<RepositoryPlugin> plugins = workspace.getRepositories();
 
 		if (useStrategy == Strategy.EXACT) {
+			if (!Verifier.isVersion(range))
+				return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version=" + range
+						+ " Invalid version", null, null);
 
 			// For an exact range we just iterate over the repos
 			// and return the first we find.
-
+			Version version = new Version(range);
 			for (RepositoryPlugin plugin : plugins) {
-				File result = plugin.get(bsn, range, Strategy.EXACT, attrs);
+				DownloadBlocker blocker = new DownloadBlocker(this);
+				File result = plugin.get(bsn, version, attrs, blocker);
 				if (result != null)
-					return toContainer(bsn, range, attrs, result);
+					return toContainer(bsn, range, attrs, result, blocker);
 			}
 		} else {
 			VersionRange versionRange = "latest".equals(range) ? new VersionRange("0") : new VersionRange(range);
@@ -959,7 +836,7 @@
 			SortedMap<Version,RepositoryPlugin> versions = new TreeMap<Version,RepositoryPlugin>();
 			for (RepositoryPlugin plugin : plugins) {
 				try {
-					List<Version> vs = plugin.versions(bsn);
+					SortedSet<Version> vs = plugin.versions(bsn);
 					if (vs != null) {
 						for (Version v : vs) {
 							if (!versions.containsKey(v) && versionRange.includes(v))
@@ -974,11 +851,13 @@
 					// Repository
 					// To query, we must have a real version
 					if (!versions.isEmpty() && Verifier.isVersion(range)) {
-						File file = plugin.get(bsn, range, useStrategy, attrs);
+						Version version = new Version(range);
+						DownloadBlocker blocker = new DownloadBlocker(this);
+						File file = plugin.get(bsn, version, attrs, blocker);
 						// and the entry must exist
 						// if it does, return this as a result
 						if (file != null)
-							return toContainer(bsn, range, attrs, file);
+							return toContainer(bsn, range, attrs, file, blocker);
 					}
 				}
 			}
@@ -1004,9 +883,10 @@
 				if (provider != null) {
 					RepositoryPlugin repo = versions.get(provider);
 					String version = provider.toString();
-					File result = repo.get(bsn, version, Strategy.EXACT, attrs);
+					DownloadBlocker blocker = new DownloadBlocker(this);
+					File result = repo.get(bsn, provider, attrs, blocker);
 					if (result != null)
-						return toContainer(bsn, version, attrs, result);
+						return toContainer(bsn, version, attrs, result, blocker);
 				} else
 					msgs.FoundVersions_ForStrategy_ButNoProvider(versions, useStrategy);
 			}
@@ -1016,7 +896,7 @@
 		// If we get this far we ran into an error somewhere
 
 		return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version=" + range + " Not found in "
-				+ plugins, null);
+				+ plugins, null, null);
 
 	}
 
@@ -1048,15 +928,19 @@
 	 * @param result
 	 * @return
 	 */
-	protected Container toContainer(String bsn, String range, Map<String,String> attrs, File result) {
+	protected Container toContainer(String bsn, String range, Map<String,String> attrs, File result, DownloadBlocker db) {
 		File f = result;
 		if (f == null) {
 			msgs.ConfusedNoContainerFile();
 			f = new File("was null");
 		}
+		Container container;
 		if (f.getName().endsWith("lib"))
-			return new Container(this, bsn, range, Container.TYPE.LIBRARY, f, null, attrs);
-		return new Container(this, bsn, range, Container.TYPE.REPO, f, null, attrs);
+			container = new Container(this, bsn, range, Container.TYPE.LIBRARY, f, null, attrs, db);
+		else
+			container = new Container(this, bsn, range, Container.TYPE.REPO, f, null, attrs, db);
+		
+		return container;
 	}
 
 	/**
@@ -1149,16 +1033,16 @@
 		}
 		File[] outputs = getBuildFiles();
 		for (File output : outputs) {
-				for (Deploy d : getPlugins(Deploy.class)) {
-					trace("Deploying %s to: %s", output.getName(), d);
-					try {
-						if (d.deploy(this, output.getName(), new BufferedInputStream(new FileInputStream(output))))
-							trace("deployed %s successfully to %s", output, d);
-					}
-					catch (Exception e) {
-						msgs.Deploying(e);
-					}
+			for (Deploy d : getPlugins(Deploy.class)) {
+				trace("Deploying %s to: %s", output.getName(), d);
+				try {
+					if (d.deploy(this, output.getName(), new BufferedInputStream(new FileInputStream(output))))
+						trace("deployed %s successfully to %s", output, d);
 				}
+				catch (Exception e) {
+					msgs.Deploying(e);
+				}
+			}
 		}
 	}
 
@@ -1265,11 +1149,11 @@
 	 * Check if this project needs building. This is defined as:
 	 */
 	public boolean isStale() throws Exception {
-		if ( workspace.isOffline()) {
+		if (workspace.isOffline()) {
 			trace("working %s offline, so always stale", this);
 			return true;
 		}
-		
+
 		Set<Project> visited = new HashSet<Project>();
 		return isStale(visited);
 	}
@@ -1316,7 +1200,7 @@
 					return true;
 			}
 		}
-		
+
 		return false;
 	}
 
@@ -1661,7 +1545,8 @@
 		return jar;
 	}
 
-	public String _project(@SuppressWarnings("unused") String args[]) {
+	public String _project(@SuppressWarnings("unused")
+	String args[]) {
 		return getBase().getAbsolutePath();
 	}
 
@@ -1722,7 +1607,7 @@
 
 	boolean replace(File f, String pattern, String replacement) throws IOException {
 		final Macro macro = getReplacer();
-		Sed sed = new Sed( new Replacer() {
+		Sed sed = new Sed(new Replacer() {
 			public String process(String line) {
 				return macro.process(line);
 			}
@@ -1755,7 +1640,8 @@
 	/**
 	 * Run all before command plugins
 	 */
-	void before(@SuppressWarnings("unused") Project p, String a) {
+	void before(@SuppressWarnings("unused")
+	Project p, String a) {
 		List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
 		for (CommandPlugin testPlugin : testPlugins) {
 			testPlugin.before(this, a);
@@ -1765,7 +1651,8 @@
 	/**
 	 * Run all after command plugins
 	 */
-	void after(@SuppressWarnings("unused") Project p, String a, Throwable t) {
+	void after(@SuppressWarnings("unused")
+	Project p, String a, Throwable t) {
 		List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
 		for (int i = testPlugins.size() - 1; i >= 0; i--) {
 			testPlugins.get(i).after(this, a, t);
@@ -1802,7 +1689,8 @@
 	}
 
 	@SuppressWarnings("unchecked")
-	public void script(@SuppressWarnings("unused") String type, String script) throws Exception {
+	public void script(@SuppressWarnings("unused")
+	String type, String script) throws Exception {
 		// TODO check tyiping
 		List<Scripter> scripters = getPlugins(Scripter.class);
 		if (scripters.isEmpty()) {
@@ -1814,7 +1702,8 @@
 		scripters.get(0).eval(x, new StringReader(script));
 	}
 
-	public String _repos(@SuppressWarnings("unused") String args[]) throws Exception {
+	public String _repos(@SuppressWarnings("unused")
+	String args[]) throws Exception {
 		List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
 		List<String> names = new ArrayList<String>();
 		for (RepositoryPlugin rp : repos)
@@ -1860,7 +1749,7 @@
 
 		for (Builder builder : builders) {
 			Container c = new Container(this, builder.getBsn(), builder.getVersion(), Container.TYPE.PROJECT,
-					getOutputFile(builder.getBsn()), null, null);
+					getOutputFile(builder.getBsn()), null, null, null);
 			result.add(c);
 		}
 		return result;
@@ -1908,7 +1797,8 @@
 	 * @return null or the builder for a sub file.
 	 * @throws Exception
 	 */
-	public Container getDeliverable(String bsn, @SuppressWarnings("unused") Map<String,String> attrs) throws Exception {
+	public Container getDeliverable(String bsn, @SuppressWarnings("unused")
+	Map<String,String> attrs) throws Exception {
 		Collection< ? extends Builder> builders = getSubBuilders();
 		for (Builder sub : builders) {
 			if (sub.getBsn().equals(bsn))
@@ -2021,13 +1911,55 @@
 	 */
 	public void setPackageInfo(String packageName, Version version) {
 		try {
-			updatePackageInfoFile(packageName, version);
+			Version current = getPackageInfoJavaVersion(packageName);
+			boolean packageInfoJava = false;
+			if (current != null) {
+				updatePackageInfoJavaFile(packageName, version);
+				packageInfoJava = true;
+			}
+			if (!packageInfoJava || getPackageInfoFile(packageName).exists()) {
+				updatePackageInfoFile(packageName, version);
+			}
 		}
 		catch (Exception e) {
 			msgs.SettingPackageInfoException_(e);
 		}
 	}
 
+	void updatePackageInfoJavaFile(String packageName, final Version newVersion) throws Exception {
+		File file = getPackageInfoJavaFile(packageName);
+
+		if (!file.exists()) {
+			return;
+		}
+
+		// If package/classes are copied into the bundle through Private-Package
+		// etc, there will be no source
+		if (!file.getParentFile().exists()) {
+			return;
+		}
+
+		Version oldVersion = getPackageInfo(packageName);
+
+		if (newVersion.compareTo(oldVersion) == 0) {
+			return;
+		}
+
+		Sed sed = new Sed(new Replacer() {
+			public String process(String line) {
+				Matcher m = VERSION_ANNOTATION.matcher(line);
+				if (m.find()) {
+					return line.substring(0, m.start(3)) + newVersion.toString() + line.substring(m.end(3));
+				}
+				return line;
+			}
+		}, file);
+
+		sed.replace(VERSION_ANNOTATION.pattern(), "$0");
+		sed.setBackup(false);
+		sed.doIt();
+	}
+
 	void updatePackageInfoFile(String packageName, Version newVersion) throws Exception {
 
 		File file = getPackageInfoFile(packageName);
@@ -2038,7 +1970,10 @@
 			return;
 		}
 
-		Version oldVersion = getPackageInfo(packageName);
+		Version oldVersion = getPackageInfoVersion(packageName);
+		if (oldVersion == null) {
+			oldVersion = Version.emptyVersion;
+		}
 
 		if (newVersion.compareTo(oldVersion) == 0) {
 			return;
@@ -2055,8 +1990,6 @@
 			throw new IOException("Could not create directory " + bp);
 		}
 		IO.copy(file, binary);
-
-		refresh();
 	}
 
 	File getPackageInfoFile(String packageName) {
@@ -2065,10 +1998,31 @@
 
 	}
 
+	File getPackageInfoJavaFile(String packageName) {
+		String path = packageName.replace('.', '/') + "/package-info.java";
+		return IO.getFile(getSrc(), path);
+
+	}
+
 	public Version getPackageInfo(String packageName) throws IOException {
+
+		Version version = getPackageInfoJavaVersion(packageName);
+		if (version != null) {
+			return version;
+		}
+		
+		version = getPackageInfoVersion(packageName);
+		if (version != null) {
+			return version;
+		}
+
+		return Version.emptyVersion;
+	}
+
+	Version getPackageInfoVersion(String packageName) throws IOException {
 		File packageInfoFile = getPackageInfoFile(packageName);
 		if (!packageInfoFile.exists()) {
-			return Version.emptyVersion;
+			return null;
 		}
 		BufferedReader reader = null;
 		try {
@@ -2086,7 +2040,31 @@
 				IO.close(reader);
 			}
 		}
-		return Version.emptyVersion;
+		return null;
+	}
+
+	Version getPackageInfoJavaVersion(String packageName) throws IOException {
+		File packageInfoJavaFile = getPackageInfoJavaFile(packageName);
+		if (!packageInfoJavaFile.exists()) {
+			return null;
+		}
+		BufferedReader reader = null;
+		try {
+			reader = IO.reader(packageInfoJavaFile);
+			String line;
+			while ((line = reader.readLine()) != null) {
+				Matcher matcher = VERSION_ANNOTATION.matcher(line);
+				if (matcher.find()) {
+					return Version.parseVersion(matcher.group(3));
+				}
+			}
+		}
+		finally {
+			if (reader != null) {
+				IO.close(reader);
+			}
+		}
+		return null;
 	}
 
 	/**
@@ -2098,7 +2076,7 @@
 		if (!f.isFile() && !f.isDirectory()) {
 			msgs.AddingNonExistentFileToClassPath_(f);
 		}
-		Container container = new Container(f);
+		Container container = new Container(f, null);
 		classpath.add(container);
 	}
 
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java b/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
index 774934a..bb39983 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
@@ -3,11 +3,16 @@
 import java.io.*;
 import java.util.*;
 
+import aQute.bnd.differ.*;
+import aQute.bnd.differ.Baseline.Info;
 import aQute.bnd.osgi.*;
+import aQute.bnd.service.*;
+import aQute.bnd.version.*;
 
 public class ProjectBuilder extends Builder {
-	Project	project;
-	boolean	initialized;
+	private final DiffPluginImpl	differ	= new DiffPluginImpl();
+	Project							project;
+	boolean							initialized;
 
 	public ProjectBuilder(Project project) {
 		super(project);
@@ -81,4 +86,92 @@
 	protected void changedFile(File f) {
 		project.getWorkspace().changedFile(f);
 	}
+
+	/**
+	 * Compare this builder's JAR with a baseline
+	 * 
+	 * @throws Exception
+	 */
+	@Override
+	protected void doBaseline(Jar dot) throws Exception {
+
+		Jar jar = getBaselineJar(false);
+		if (jar == null) {
+			return;
+		}
+		try {
+			Baseline baseline = new Baseline(this, differ);
+
+			Set<Info> infos = baseline.baseline(dot, jar, 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);
+				}
+			}
+		}
+		finally {
+			jar.close();
+		}
+	}
+
+	protected Jar getBaselineJar(boolean fallback) throws Exception {
+
+		String baseline = getProperty(Constants.BASELINE);
+		if ((baseline == null || baseline.trim().length() == 0) && !fallback)
+			return null;
+
+		trace("baseline %s", baseline);
+
+		File baselineFile = null;
+		if ((baseline == null || baseline.trim().length() == 0) && fallback) {
+
+			String repoName = getProperty(Constants.BASELINEREPO);
+			if (repoName == null) {
+				repoName = getProperty(Constants.RELEASEREPO);
+				if (repoName == null) {
+					return null;
+				}
+			}
+
+			List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
+			for (RepositoryPlugin repo : repos) {
+				if (repoName.equals(repo.getName())) {
+					SortedSet<Version> versions = repo.versions(getBsn());
+					if (!versions.isEmpty()) {
+						baselineFile = repo.get(getBsn(), versions.last(), null);
+					}
+					break;
+				}
+			}
+		} else {
+
+			Collection<Container> bundles = project.getBundles(Strategy.LOWEST, baseline);
+			for (Container c : bundles) {
+
+				if (c.getError() != null || c.getFile() == null) {
+					error("Erroneous baseline bundle %s", c);
+					continue;
+				}
+
+				baselineFile = c.getFile();
+				break;
+			}
+		}
+		if (fallback && baselineFile == null) {
+			return new Jar(".");
+		}
+		return new Jar(baselineFile);
+	}
+
+	/** 
+	 * Gets the baseline Jar. 
+	 * 
+	 * @return the baseline jar
+	 * @throws Exception
+	 */
+	public Jar getBaselineJar() throws Exception {
+		return getBaselineJar(true);
+	}
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java b/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
index 7c85fb3..3507f53 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
@@ -8,7 +8,7 @@
 
 import aQute.bnd.header.*;
 import aQute.bnd.osgi.*;
-import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.bnd.service.*;
 import aQute.libg.command.*;
 import aQute.libg.generics.*;
 
@@ -97,8 +97,7 @@
 		timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
 		trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
 
-		// For backward compatibility with bndtools launcher
-		List<Container> fws = project.getBundles(Strategy.HIGHEST, project.getProperty("-runfw"), "-runfw");
+		List<Container> fws = project.getBundles(Strategy.HIGHEST, project.getProperty(Constants.RUNFW), Constants.RUNFW);
 		runpath.addAll(fws);
 
 		for (Container c : runpath) {
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/ProjectMessages.java b/bundleplugin/src/main/java/aQute/bnd/build/ProjectMessages.java
index f692abb..c2908a4 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/ProjectMessages.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/ProjectMessages.java
@@ -4,7 +4,6 @@
 import java.util.*;
 
 import aQute.bnd.service.*;
-import aQute.bnd.service.RepositoryPlugin.Strategy;
 import aQute.bnd.version.*;
 import aQute.service.reporter.*;
 
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java b/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
index 0044b11..be7a71c 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
@@ -2,6 +2,7 @@
 
 import java.io.*;
 import java.lang.ref.*;
+import java.net.*;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.locks.*;
@@ -14,7 +15,9 @@
 import aQute.bnd.service.*;
 import aQute.bnd.service.action.*;
 import aQute.lib.deployer.*;
+import aQute.lib.hex.*;
 import aQute.lib.io.*;
+import aQute.lib.settings.*;
 import aQute.service.reporter.*;
 
 public class Workspace extends Processor {
@@ -29,6 +32,7 @@
 	final File									buildDir;
 	final Maven									maven		= new Maven(Processor.getExecutor());
 	private boolean								offline		= true;
+	Settings									settings	= new Settings();
 
 	/**
 	 * This static method finds the workspace and creates a project (or returns
@@ -157,7 +161,8 @@
 		}
 	}
 
-	public String _workspace(@SuppressWarnings("unused") String args[]) {
+	public String _workspace(@SuppressWarnings("unused")
+	String args[]) {
 		return getBase().getAbsolutePath();
 	}
 
@@ -270,11 +275,11 @@
 		}
 
 		@Override
-		protected void init() throws Exception {
+		protected boolean init() throws Exception {
 			if (lock.tryLock(50, TimeUnit.SECONDS) == false)
 				throw new TimeLimitExceededException("Cached File Repo is locked and can't acquire it");
 			try {
-				if (!inited) {
+				if (super.init()) {
 					inited = true;
 					if (!root.exists() && !root.mkdirs()) {
 						throw new IOException("Could not create cache directory " + root);
@@ -288,7 +293,9 @@
 					else {
 						error("Couldn't find embedded-repo.jar in bundle ");
 					}
-				}
+					return true;
+				} else
+					return false;
 			}
 			finally {
 				lock.unlock();
@@ -386,4 +393,31 @@
 		this.offline = on;
 		return this;
 	}
+
+	/**
+	 * Provide access to the global settings of this machine.
+	 * 
+	 * @throws Exception
+	 * @throws UnknownHostException
+	 */
+
+	public String _global(String[] args) throws Exception {
+		Macro.verifyCommand(args, "${global;<name>[;<default>]}, get a global setting from ~/.bnd/settings.json", null,
+				2, 3);
+
+		String key = args[1];
+		if (key.equals("key.public"))
+			return Hex.toHexString(settings.getPublicKey());
+		if (key.equals("key.private"))
+			return Hex.toHexString(settings.getPrivateKey());
+
+		String s = settings.get(key);
+		if (s != null)
+			return s;
+
+		if (args.length == 3)
+			return args[2];
+		
+		return null;
+	}
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/WorkspaceRepository.java b/bundleplugin/src/main/java/aQute/bnd/build/WorkspaceRepository.java
index c52b7f1..88ead98 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/WorkspaceRepository.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/WorkspaceRepository.java
@@ -7,8 +7,10 @@
 import aQute.bnd.osgi.*;
 import aQute.bnd.service.*;
 import aQute.bnd.version.*;
+import aQute.lib.collections.*;
+import aQute.libg.glob.*;
 
-public class WorkspaceRepository implements RepositoryPlugin {
+public class WorkspaceRepository implements RepositoryPlugin, Actionable {
 	private final Workspace	workspace;
 
 	public WorkspaceRepository(Workspace workspace) {
@@ -87,10 +89,10 @@
 	}
 
 	public PutResult put(InputStream stream, PutOptions options) throws Exception {
-		return null;
+		throw new UnsupportedOperationException("Read only repository");
 	}
 
-	public List<String> list(String regex) throws Exception {
+	public List<String> list(String pattern) throws Exception {
 		List<String> names = new ArrayList<String>();
 		Collection<Project> projects = workspace.getAllProjects();
 		for (Project project : projects) {
@@ -99,9 +101,9 @@
 				for (File file : build) {
 					Jar jar = new Jar(file);
 					String bsn = jar.getBsn();
-					if (regex != null) {
-						Pattern pattern = Pattern.compile(regex);
-						Matcher matcher = pattern.matcher(bsn);
+					if (pattern != null) {
+						Glob glob = new Glob(pattern);
+						Matcher matcher = glob.matcher(bsn);
 						if (matcher.matches()) {
 							if (!names.contains(bsn)) {
 								names.add(bsn);
@@ -119,7 +121,7 @@
 		return names;
 	}
 
-	public List<Version> versions(String bsn) throws Exception {
+	public SortedSet<Version> versions(String bsn) throws Exception {
 		List<Version> versions = new ArrayList<Version>();
 		Collection<Project> projects = workspace.getAllProjects();
 		for (Project project : projects) {
@@ -127,22 +129,66 @@
 			if (build != null) {
 				for (File file : build) {
 					Jar jar = new Jar(file);
-					if (bsn.equals(jar.getBsn())) {
-						versions.add(new Version(jar.getVersion()));
+					try {
+						if (bsn.equals(jar.getBsn())) {
+							String v  = jar.getVersion();
+							if ( v == null)
+								v = "0";
+							else if (!Verifier.isVersion(v))
+								continue; // skip
+							
+							versions.add(new Version(v));
+						}
+					}
+					finally {
+						jar.close();
 					}
 				}
 			}
 		}
-
-		return versions;
+		if ( versions.isEmpty())
+			return SortedList.empty();
+		
+		return new SortedList<Version>(versions);
 	}
 
 	public String getName() {
-		return "Workspace";
+		return "Workspace " + workspace.getBase().getName();
 	}
 
 	public String getLocation() {
-		return "Workspace";
+		return workspace.getBase().getAbsolutePath();
+	}
+
+	public File get(String bsn, Version version, Map<String,String> properties, DownloadListener ... listeners) throws Exception {
+		File file = get(bsn, version.toString(), Strategy.EXACT, properties);
+		if ( file == null)
+			return null;
+		for (DownloadListener l : listeners) {
+			try {
+				l.success(file);
+			}
+			catch (Exception e) {
+				workspace.exception(e, "Workspace repo listener callback for %s" ,file);
+			}
+		}
+		return file;
+	}
+
+	
+	public Map<String,Runnable> actions(Object... target) throws Exception {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public String tooltip(Object... target) throws Exception {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public String title(Object... target) throws Exception {
+		// TODO Auto-generated method stub
+		return null;
 	}
 
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/model/BndEditModel.java b/bundleplugin/src/main/java/aQute/bnd/build/model/BndEditModel.java
index 35cd3c6..5a6ca78 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/model/BndEditModel.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/model/BndEditModel.java
@@ -24,19 +24,22 @@
  */
 public class BndEditModel {
 
-	public static final String										LINE_SEPARATOR				= " \\\n\t";
+	public static final String										NEWLINE_LINE_SEPARATOR		= "\\n\\\n\t";
 	public static final String										LIST_SEPARATOR				= ",\\\n\t";
 
 	private static final String									ISO_8859_1					= "ISO-8859-1";												//$NON-NLS-1$
 
 	private static String[]										KNOWN_PROPERTIES			= new String[] {
+			Constants.BUNDLE_LICENSE, Constants.BUNDLE_CATEGORY,
+			Constants.BUNDLE_NAME, Constants.BUNDLE_DESCRIPTION, Constants.BUNDLE_COPYRIGHT, Constants.BUNDLE_UPDATELOCATION,
+			Constants.BUNDLE_VENDOR, Constants.BUNDLE_CONTACTADDRESS, Constants.BUNDLE_DOCURL,
 			Constants.BUNDLE_SYMBOLICNAME, Constants.BUNDLE_VERSION, Constants.BUNDLE_ACTIVATOR,
 			Constants.EXPORT_PACKAGE, Constants.IMPORT_PACKAGE, aQute.bnd.osgi.Constants.PRIVATE_PACKAGE,
 			aQute.bnd.osgi.Constants.SOURCES,
 			aQute.bnd.osgi.Constants.SERVICE_COMPONENT, aQute.bnd.osgi.Constants.CLASSPATH,
 			aQute.bnd.osgi.Constants.BUILDPATH, aQute.bnd.osgi.Constants.BUILDPACKAGES,
 			aQute.bnd.osgi.Constants.RUNBUNDLES, aQute.bnd.osgi.Constants.RUNPROPERTIES, aQute.bnd.osgi.Constants.SUB,
-			aQute.bnd.osgi.Constants.RUNFRAMEWORK,
+			aQute.bnd.osgi.Constants.RUNFRAMEWORK, aQute.bnd.osgi.Constants.RUNFW,
 			aQute.bnd.osgi.Constants.RUNVM,
 			// BndConstants.RUNVMARGS,
 			// BndConstants.TESTSUITES,
@@ -134,7 +137,6 @@
 	// EnumConverter.create(ResolveMode.class, ResolveMode.manual);
 
 	// FORMATTERS
-	private Converter<String,Object>								defaultFormatter			= new DefaultFormatter();
 	private Converter<String,String>								newlineEscapeFormatter		= new NewlineEscapedStringFormatter();
 	private Converter<String,Boolean>								defaultFalseBoolFormatter	= new DefaultBooleanFormatter(
 																										false);
@@ -166,6 +168,15 @@
 	@SuppressWarnings("deprecation")
 	public BndEditModel() {
 		// register converters
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_LICENSE, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_CATEGORY, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_NAME, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_DESCRIPTION, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_COPYRIGHT, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_UPDATELOCATION, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_VENDOR, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_CONTACTADDRESS, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.BUNDLE_DOCURL, stringConverter);
 		converters.put(aQute.bnd.osgi.Constants.BUILDPATH, buildPathConverter);
 		converters.put(aQute.bnd.osgi.Constants.BUILDPACKAGES, buildPackagesConverter);
 		converters.put(aQute.bnd.osgi.Constants.RUNBUNDLES, clauseListConverter);
@@ -180,6 +191,7 @@
 		converters.put(aQute.bnd.osgi.Constants.SERVICE_COMPONENT, serviceComponentConverter);
 		converters.put(Constants.IMPORT_PACKAGE, importPatternConverter);
 		converters.put(aQute.bnd.osgi.Constants.RUNFRAMEWORK, stringConverter);
+		converters.put(aQute.bnd.osgi.Constants.RUNFW, stringConverter);
 		converters.put(aQute.bnd.osgi.Constants.SUB, listConverter);
 		converters.put(aQute.bnd.osgi.Constants.RUNPROPERTIES, propertiesConverter);
 		converters.put(aQute.bnd.osgi.Constants.RUNVM, stringConverter);
@@ -192,6 +204,16 @@
 		converters.put(aQute.bnd.osgi.Constants.RUNREPOS, listConverter);
 		// converters.put(BndConstants.RESOLVE_MODE, resolveModeConverter);
 
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_LICENSE, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_CATEGORY, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_NAME, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_DESCRIPTION, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_COPYRIGHT, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_UPDATELOCATION, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_VENDOR, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_CONTACTADDRESS, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.BUNDLE_DOCURL, newlineEscapeFormatter);
+
 		formatters.put(aQute.bnd.osgi.Constants.BUILDPATH, headerClauseListFormatter);
 		formatters.put(aQute.bnd.osgi.Constants.BUILDPACKAGES, headerClauseListFormatter);
 		formatters.put(aQute.bnd.osgi.Constants.RUNBUNDLES, headerClauseListFormatter);
@@ -206,6 +228,7 @@
 		formatters.put(aQute.bnd.osgi.Constants.SERVICE_COMPONENT, headerClauseListFormatter);
 		formatters.put(Constants.IMPORT_PACKAGE, headerClauseListFormatter);
 		formatters.put(aQute.bnd.osgi.Constants.RUNFRAMEWORK, newlineEscapeFormatter);
+		formatters.put(aQute.bnd.osgi.Constants.RUNFW, newlineEscapeFormatter);
 		formatters.put(aQute.bnd.osgi.Constants.SUB, stringListFormatter);
 		formatters.put(aQute.bnd.osgi.Constants.RUNPROPERTIES, propertiesFormatter);
 		formatters.put(aQute.bnd.osgi.Constants.RUNVM, newlineEscapeFormatter);
@@ -340,6 +363,78 @@
 		doSetObject(propertyName, oldValue, value, formatter);
 	}
 
+	public String getBundleLicense() {
+		return doGetObject(Constants.BUNDLE_LICENSE, stringConverter);
+	}
+
+	public void setBundleLicense(String bundleLicense) {
+		doSetObject(Constants.BUNDLE_LICENSE, getBundleLicense(), bundleLicense, newlineEscapeFormatter);
+	}
+
+	public String getBundleCategory() {
+		return doGetObject(Constants.BUNDLE_CATEGORY, stringConverter);
+	}
+
+	public void setBundleCategory(String bundleCategory) {
+		doSetObject(Constants.BUNDLE_CATEGORY, getBundleCategory(), bundleCategory, newlineEscapeFormatter);
+	}
+
+	public String getBundleName() {
+		return doGetObject(Constants.BUNDLE_NAME, stringConverter);
+	}
+
+	public void setBundleName(String bundleName) {
+		doSetObject(Constants.BUNDLE_NAME, getBundleName(), bundleName, newlineEscapeFormatter);
+	}
+
+	public String getBundleDescription() {
+		return doGetObject(Constants.BUNDLE_DESCRIPTION, stringConverter);
+	}
+
+	public void setBundleDescription(String bundleDescription) {
+		doSetObject(Constants.BUNDLE_DESCRIPTION, getBundleDescription(), bundleDescription, newlineEscapeFormatter);
+	}
+
+	public String getBundleCopyright() {
+		return doGetObject(Constants.BUNDLE_COPYRIGHT, stringConverter);
+	}
+
+	public void setBundleCopyright(String bundleCopyright) {
+		doSetObject(Constants.BUNDLE_COPYRIGHT, getBundleCopyright(), bundleCopyright, newlineEscapeFormatter);
+	}
+
+	public String getBundleUpdateLocation() {
+		return doGetObject(Constants.BUNDLE_UPDATELOCATION, stringConverter);
+	}
+
+	public void setBundleUpdateLocation(String bundleUpdateLocation) {
+		doSetObject(Constants.BUNDLE_UPDATELOCATION, getBundleUpdateLocation(), bundleUpdateLocation, newlineEscapeFormatter);
+	}
+
+	public String getBundleVendor() {
+		return doGetObject(Constants.BUNDLE_VENDOR, stringConverter);
+	}
+
+	public void setBundleVendor(String bundleVendor) {
+		doSetObject(Constants.BUNDLE_VENDOR, getBundleVendor(), bundleVendor, newlineEscapeFormatter);
+	}
+
+	public String getBundleContactAddress() {
+		return doGetObject(Constants.BUNDLE_CONTACTADDRESS, stringConverter);
+	}
+
+	public void setBundleContactAddress(String bundleContactAddress) {
+		doSetObject(Constants.BUNDLE_CONTACTADDRESS, getBundleContactAddress(), bundleContactAddress, newlineEscapeFormatter);
+	}
+
+	public String getBundleDocUrl() {
+		return doGetObject(Constants.BUNDLE_DOCURL, stringConverter);
+	}
+
+	public void setBundleDocUrl(String bundleDocUrl) {
+		doSetObject(Constants.BUNDLE_DOCURL, getBundleDocUrl(), bundleDocUrl, newlineEscapeFormatter);
+	}
+
 	public String getBundleSymbolicName() {
 		return doGetObject(Constants.BUNDLE_SYMBOLICNAME, stringConverter);
 	}
@@ -610,6 +705,10 @@
         return doGetObject(aQute.bnd.osgi.Constants.RUNFRAMEWORK, stringConverter);
     }
 
+    public String getRunFw() {
+        return doGetObject(aQute.bnd.osgi.Constants.RUNFW, stringConverter);
+    }
+
     public EE getEE() {
         return doGetObject(aQute.bnd.osgi.Constants.RUNEE, eeConverter);
     }
@@ -621,10 +720,17 @@
 
     
     public void setRunFramework(String clause) {
+        assert (Constants.RUNFRAMEWORK_SERVICES.equals(clause.toLowerCase().trim()) ||
+                Constants.RUNFRAMEWORK_NONE.equals(clause.toLowerCase().trim()));
         String oldValue = getRunFramework();
         doSetObject(aQute.bnd.osgi.Constants.RUNFRAMEWORK, oldValue, clause, newlineEscapeFormatter);
     }
     
+    public void setRunFw(String clause) {
+        String oldValue = getRunFw();
+        doSetObject(aQute.bnd.osgi.Constants.RUNFW, oldValue, clause, newlineEscapeFormatter);
+    }
+
     public List<Requirement> getRunRequires() {
     	return doGetObject(aQute.bnd.osgi.Constants.RUNREQUIRES, requirementListConverter);
     }
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/model/conversions/NewlineEscapedStringFormatter.java b/bundleplugin/src/main/java/aQute/bnd/build/model/conversions/NewlineEscapedStringFormatter.java
index 790cda0..1bf9220 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/model/conversions/NewlineEscapedStringFormatter.java
+++ b/bundleplugin/src/main/java/aQute/bnd/build/model/conversions/NewlineEscapedStringFormatter.java
@@ -23,7 +23,7 @@
 				break;
 			}
 			result.append(input.substring(position, newlineIndex));
-			result.append(BndEditModel.LINE_SEPARATOR);
+			result.append(BndEditModel.NEWLINE_LINE_SEPARATOR);
 			position = newlineIndex + 1;
 		}
 
diff --git a/bundleplugin/src/main/java/aQute/bnd/build/model/packageinfo b/bundleplugin/src/main/java/aQute/bnd/build/model/packageinfo
index 55af8e5..5d22684 100644
--- a/bundleplugin/src/main/java/aQute/bnd/build/model/packageinfo
+++ b/bundleplugin/src/main/java/aQute/bnd/build/model/packageinfo
@@ -1 +1 @@
-version 2
\ No newline at end of file
+version 2.1
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java b/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
index b58b775..b368f0b 100644
--- a/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
+++ b/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
@@ -112,7 +112,7 @@
 	}
 	
 	void sortReferences() {
-		Map<String, ReferenceDef> temp = new TreeMap(references);
+		Map<String, ReferenceDef> temp = new TreeMap<String,ReferenceDef>(references);
 		references.clear();
 		references.putAll(temp);
 	}
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/HeaderReader.java b/bundleplugin/src/main/java/aQute/bnd/component/HeaderReader.java
index aa1e6e7..f57b7cb 100644
--- a/bundleplugin/src/main/java/aQute/bnd/component/HeaderReader.java
+++ b/bundleplugin/src/main/java/aQute/bnd/component/HeaderReader.java
@@ -34,8 +34,8 @@
 	private final static String BundleContextTR = "org.osgi.framework.BundleContext";
 	private final static String MapTR = Map.class.getName();
 	private final static String IntTR = int.class.getName();
-	private final static Set<String> allowed = new HashSet<String>(Arrays.asList(ComponentContextTR, BundleContextTR, MapTR));
-	private final static Set<String> allowedDeactivate = new HashSet<String>(Arrays.asList(ComponentContextTR, BundleContextTR, MapTR, IntTR));
+	final static Set<String> allowed = new HashSet<String>(Arrays.asList(ComponentContextTR, BundleContextTR, MapTR));
+	final static Set<String> allowedDeactivate = new HashSet<String>(Arrays.asList(ComponentContextTR, BundleContextTR, MapTR, IntTR));
 	
 	private final static String ServiceReferenceTR = "org.osgi.framework.ServiceReference";
 
@@ -291,7 +291,7 @@
 	 * @param allowedParams TODO
 	 * @return rating; 6 if invalid, lower is better
 	 */
-	private int rateLifecycle(MethodDef test, Set<String> allowedParams) {
+	int rateLifecycle(MethodDef test, Set<String> allowedParams) {
 		TypeRef[] prototype = test.getDescriptor().getPrototype();
 		if (prototype.length == 1 && ComponentContextTR.equals(prototype[0].getFQN()))
 			    return 1;
@@ -317,7 +317,7 @@
 	 * @param test
 	 * @return
 	 */
-	private int rateBind(MethodDef test) {
+	int rateBind(MethodDef test) {
 		TypeRef[] prototype = test.getDescriptor().getPrototype();
 		if (prototype.length == 1 && ServiceReferenceTR.equals(prototype[0].getFQN()))
 			return 1;
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java b/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java
index 4da23cc..38de872 100644
--- a/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java
@@ -10,6 +10,7 @@
 import aQute.bnd.osgi.*;
 import aQute.bnd.service.diff.*;
 import aQute.bnd.service.diff.Tree.Data;
+import aQute.lib.collections.*;
 import aQute.lib.hex.*;
 import aQute.lib.io.*;
 import aQute.libg.cryptography.*;
@@ -153,7 +154,13 @@
 				for (Map.Entry<String,Attrs> clause : clauses.entrySet()) {
 					Collection<Element> parameterDef = new ArrayList<Element>();
 					for (Map.Entry<String,String> parameter : clause.getValue().entrySet()) {
-						parameterDef.add(new Element(Type.PARAMETER, parameter.getKey() + ":" + parameter.getValue(),
+						String paramValue = parameter.getValue();
+						if (Constants.EXPORT_PACKAGE.equals(header) && Constants.USES_DIRECTIVE.equals(parameter.getKey())) {
+							ExtList<String> uses = ExtList.from(parameter.getValue());
+							Collections.sort(uses);
+							paramValue = uses.join();
+						}
+						parameterDef.add(new Element(Type.PARAMETER, parameter.getKey() + ":" + paramValue,
 								null, CHANGED, CHANGED, null));
 					}
 					clausesDef.add(new Element(Type.CLAUSE, clause.getKey(), parameterDef, CHANGED, CHANGED, null));
diff --git a/bundleplugin/src/main/java/aQute/bnd/filerepo/FileRepo.java b/bundleplugin/src/main/java/aQute/bnd/filerepo/FileRepo.java
index a8966a4..548ace7 100644
--- a/bundleplugin/src/main/java/aQute/bnd/filerepo/FileRepo.java
+++ b/bundleplugin/src/main/java/aQute/bnd/filerepo/FileRepo.java
@@ -8,7 +8,7 @@
 
 public class FileRepo {
 	File	root;
-	Pattern	REPO_FILE	= Pattern.compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+|latest)\\.(jar|lib)");
+	Pattern	REPO_FILE	= Pattern.compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+)\\.(jar|lib)");
 
 	public FileRepo(File root) {
 		this.root = root;
diff --git a/bundleplugin/src/main/java/aQute/bnd/help/Syntax.java b/bundleplugin/src/main/java/aQute/bnd/help/Syntax.java
index 2b6ccbf..84e6234 100644
--- a/bundleplugin/src/main/java/aQute/bnd/help/Syntax.java
+++ b/bundleplugin/src/main/java/aQute/bnd/help/Syntax.java
@@ -15,25 +15,25 @@
 
 	static Syntax							version					= new Syntax(
 																			VERSION_ATTRIBUTE,
-																			"A version range to select the version of an export definition. The default value is 0.0.0 .",
-																			"version=\"[1.2,3.0)\"", null,
+																			"A version range to select the version of an export definition. The default value is 0.0.0.",
+																			VERSION_ATTRIBUTE + "=\"[1.2,3.0)\"", null,
 																			Verifier.VERSIONRANGE);
 	static Syntax							bundle_symbolic_name	= new Syntax(
 																			BUNDLE_SYMBOLIC_NAME_ATTRIBUTE,
 																			"The bundle symbolic name of the exporting bundle.",
-																			"bundle-symbolic-name=com.acme.foo.daffy",
+																			BUNDLE_SYMBOLIC_NAME_ATTRIBUTE + "=com.acme.foo.daffy",
 																			null, Verifier.SYMBOLICNAME);
 
 	static Syntax							bundle_version			= new Syntax(
 																			BUNDLE_VERSION_ATTRIBUTE,
-																			"a version range to select the bundle version of the exporting bundle. The default value is 0.0.0.",
-																			"bundle-version=1.3", null,
+																			"A version range to select the bundle version of the exporting bundle. The default value is 0.0.0.",
+																			BUNDLE_VERSION_ATTRIBUTE + "=1.3", null,
 																			Verifier.VERSIONRANGE);
 
 	static Syntax							path_version			= new Syntax(
 																			VERSION_ATTRIBUTE,
-																			"Specifies the range in the repository, project, or file",
-																			"version=project", "project,type", Pattern
+																			"Specifies the range in the repository, project or file.",
+																			VERSION_ATTRIBUTE + "=project", "project,type", Pattern
 																					.compile("project|type|"
 																							+ Verifier.VERSIONRANGE
 																									.toString()));
@@ -41,94 +41,94 @@
 	static Syntax[]							syntaxes				= new Syntax[] {
 			new Syntax(
 					BUNDLE_ACTIVATIONPOLICY,
-					"The Bundle-ActivationPolicy specifies how the framework should activate the bundle once started. ",
-					"Bundle-ActivationPolicy: lazy", "lazy", Pattern.compile("lazy")),
+					"The " + BUNDLE_ACTIVATIONPOLICY + " header specifies how the framework should activate the bundle once started.",
+					BUNDLE_ACTIVATIONPOLICY + ": lazy", "lazy", Pattern.compile("lazy")),
 
 			new Syntax(BUNDLE_ACTIVATOR,
-					"The Bundle-Activator header specifies the name of the class used to start and stop the bundle. ",
-					"Bundle-Activator: com.acme.foo.Activator",
+					"The " + BUNDLE_ACTIVATOR + " header specifies the name of the class used to start and stop the bundle.",
+					BUNDLE_ACTIVATOR + ": com.acme.foo.Activator",
 					"${classes;implementing;org.osgi.framework.BundleActivator}", Verifier.FQNPATTERN),
-			new Syntax(BUNDLE_CATEGORY, "The Bundle-Category header holds a comma-separated list of category names",
-					"Bundle-Category: test", "osgi,test,game,util,eclipse,netbeans,jdk,specification", null),
+			new Syntax(BUNDLE_CATEGORY, "The " + BUNDLE_CATEGORY + " header holds a comma-separated list of category names.",
+					BUNDLE_CATEGORY + ": test", "osgi,test,game,util,eclipse,netbeans,jdk,specification", null),
 			new Syntax(
 					BUNDLE_CLASSPATH,
-					"The Bundle-ClassPath header defines a comma-separated list of JAR file path names or directories (inside the bundle) containing classes and resources. The period (’.’) specifies the root directory of the bundle’s JAR. The period is also the default.",
-					"Bundle-Classpath: /lib/libnewgen.so, .", null, Verifier.PATHPATTERN),
+					"The " + BUNDLE_CLASSPATH + " header defines a comma-separated list of JAR file path names or directories (inside the bundle) containing classes and resources. The period (’.’) specifies the root directory of the bundle’s JAR. The period is also the default.",
+					BUNDLE_CLASSPATH + ": /lib/libnewgen.so, .", null, Verifier.PATHPATTERN),
 			new Syntax(BUNDLE_CONTACTADDRESS,
-					"The Bundle-ContactAddress header provides the contact address of the vendor. ",
-					"Bundle-ContactAddress: 2400 Oswego Road, Austin, TX 74563", null, null),
+					"The " + BUNDLE_CONTACTADDRESS + " header provides the contact address of the vendor.",
+					BUNDLE_CONTACTADDRESS + ": 2400 Oswego Road, Austin, TX 74563", null, null),
 			new Syntax(BUNDLE_COPYRIGHT,
-					"The Bundle-Copyright header contains the copyright specification for this bundle. ",
-					"Bundle-Copyright: OSGi (c) 2002", null, null),
-			new Syntax(BUNDLE_DESCRIPTION, "The Bundle-Description header defines a short description of this bundle.",
-					"Bundle-Description: Ceci ce n'est pas une bundle", null, null),
+					"The " + BUNDLE_COPYRIGHT + " header contains the copyright specification for this bundle.",
+					BUNDLE_COPYRIGHT + ": OSGi (c) 2002", null, null),
+			new Syntax(BUNDLE_DESCRIPTION, "The " + BUNDLE_DESCRIPTION + " header defines a short description of this bundle.",
+					BUNDLE_DESCRIPTION + ": Ceci ce n'est pas une bundle", null, null),
 
 			new Syntax(BUNDLE_DOCURL,
-					"The Bundle-DocURL headers must contain a URL pointing to documentation about this bundle.",
-					"Bundle-DocURL: http://www.aQute.biz/Code/Bnd", null, Verifier.URLPATTERN),
+					"The " + BUNDLE_DOCURL + " header must contain a URL pointing to documentation about this bundle.",
+					BUNDLE_DOCURL + ": http://www.aQute.biz/Code/Bnd", null, Verifier.URLPATTERN),
 
 			new Syntax(
 					BUNDLE_ICON,
-					"The optional Bundle-Icon header provides a list of (relative) URLs to icons representing this bundle in different sizes. ",
-					"Bundle-Icon: /icons/bnd.png;size=64", "/icons/bundle.png", Verifier.URLPATTERN, new Syntax("size",
-							"Icons size in pixels, e.g. 64", "64", "16,32,48,64,128", Verifier.NUMBERPATTERN)),
+					"The optional " + BUNDLE_ICON + " header provides a list of (relative) URLs to icons representing this bundle in different sizes.",
+					BUNDLE_ICON + ": /icons/bnd.png;size=64", "/icons/bundle.png", Verifier.URLPATTERN, new Syntax("size",
+							"Icons size in pixels, e.g. 64.", "size=64", "16,32,48,64,128", Verifier.NUMBERPATTERN)),
 
 			new Syntax(
 					BUNDLE_LICENSE,
-					"The Bundle-License header provides an optional machine readable form of license information. The purpose of this header is to automate some of the license processing required by many organizations",
-					"Bundle License: http://www.opensource.org/licenses/jabberpl.php",
+					"The " + BUNDLE_LICENSE + " header provides an optional machine readable form of license information. The purpose of this header is to automate some of the license processing required by many organizations.",
+					BUNDLE_LICENSE + ": http://www.opensource.org/licenses/jabberpl.php",
 					"http://www.apache.org/licenses/LICENSE-2.0,<<EXTERNAL>>", Pattern.compile("("
 							+ Verifier.URLPATTERN + "|<<EXTERNAL>>)"), new Syntax(DESCRIPTION_ATTRIBUTE,
-							"Human readable description of the license", "description=\"Described the license here\"",
+							"Human readable description of the license.", DESCRIPTION_ATTRIBUTE + "=\"Describe the license here\"",
 							null, Verifier.ANYPATTERN), new Syntax(LINK_ATTRIBUTE, "", "", null, Verifier.URLPATTERN)),
 			new Syntax(
 					BUNDLE_LOCALIZATION,
-					"The Bundle-Localization header contains the location in the bundle where localization files can be found. The default value is OSGI-INF/l10n/bundle. Translations are by default therefore OSGI-INF/l10n/bundle_de.properties, OSGI-INF/l10n/bundle_nl.properties, etc.",
-					"Bundle-Localization: OSGI-INF/l10n/bundle", "OSGI-INF/l10n/bundle", Verifier.URLPATTERN),
+					"The " + BUNDLE_LOCALIZATION + " header contains the location in the bundle where localization files can be found. The default value is OSGI-INF/l10n/bundle. Translations are by default therefore OSGI-INF/l10n/bundle_de.properties, OSGI-INF/l10n/bundle_nl.properties, etc.",
+					BUNDLE_LOCALIZATION + ": OSGI-INF/l10n/bundle", "OSGI-INF/l10n/bundle", Verifier.URLPATTERN),
 			new Syntax(
 					BUNDLE_MANIFESTVERSION,
-					"This header is set by bnd automatically to 2. The Bundle-ManifestVersion header defines that the bundle follows the rules of this specification. The Bundle-ManifestVersion header determines whether the bundle follows the rules of this specification.",
-					"# Bundle-ManifestVersion: 2", "2", Verifier.NUMBERPATTERN),
+					"The " + BUNDLE_MANIFESTVERSION + " header is set by bnd automatically to 2. The header defines that the bundle follows the rules of this specification.",
+					"# " + BUNDLE_MANIFESTVERSION + ": 2", "2", Verifier.NUMBERPATTERN),
 			new Syntax(
 					BUNDLE_NAME,
-					"This header will be derived from the  Bundle-SymbolicName if not set. The Bundle-Name header defines a readable name for this bundle. This should be a short, human-readable name that can contain spaces.",
-					"Bundle-Name: My Bundle", null, Verifier.ANYPATTERN),
+					"The " + BUNDLE_NAME + " header will be derived from the " + BUNDLE_SYMBOLICNAME + " header if not set. The " + BUNDLE_NAME + " header defines a readable name for this bundle. This should be a short, human-readable name that can contain spaces.",
+					BUNDLE_NAME + ": My Bundle", null, Verifier.ANYPATTERN),
 			new Syntax(
 					BUNDLE_NATIVECODE,
-					"The Bundle-NativeCode header contains a specification of native code libraries contained in this bundle. ",
-					"Bundle-NativeCode: /lib/http.DLL; osname = QNX; osversion = 3.1",
+					"The " + BUNDLE_NATIVECODE + " header contains a specification of native code libraries contained in this bundle.",
+					BUNDLE_NATIVECODE + ": /lib/http.DLL; osname = QNX; osversion = 3.1",
 					null,
 					Verifier.PATHPATTERN,
-					new Syntax(OSNAME_ATTRIBUTE, "The name of the operating system", "osname=MacOS", Processor.join(
+					new Syntax(OSNAME_ATTRIBUTE, "The name of the operating system.", OSNAME_ATTRIBUTE + "=MacOS", Processor.join(
 							Verifier.OSNAMES, ","), Verifier.ANYPATTERN),
-					new Syntax(OSVERSION_ATTRIBUTE, "Operating System Version", "osversion=3.1", null,
+					new Syntax(OSVERSION_ATTRIBUTE, "Operating System Version.", OSVERSION_ATTRIBUTE + "=3.1", null,
 							Verifier.ANYPATTERN),
-					new Syntax(LANGUAGE_ATTRIBUTE, "Language ISO 639 code", "language=nl", null, Verifier.ISO639),
-					new Syntax(PROCESSOR_ATTRIBUTE, "Processor name", "processor=x86", Processor.join(
+					new Syntax(LANGUAGE_ATTRIBUTE, "Language ISO 639 code.", LANGUAGE_ATTRIBUTE + "=nl", null, Verifier.ISO639),
+					new Syntax(PROCESSOR_ATTRIBUTE, "Processor name.", PROCESSOR_ATTRIBUTE + "=x86", Processor.join(
 							Verifier.PROCESSORNAMES, ","), Verifier.ANYPATTERN),
 					new Syntax(
 							SELECTION_FILTER_ATTRIBUTE,
 							"The value of this attribute must be a filter expression that indicates if the native code clause should be selected or not.",
-							"selection-filter=\"(com.acme.windowing=win32)\"", null, Verifier.FILTERPATTERN)),
+							SELECTION_FILTER_ATTRIBUTE + "=\"(com.acme.windowing=win32)\"", null, Verifier.FILTERPATTERN)),
 			new Syntax(
 					BUNDLE_REQUIREDEXECUTIONENVIRONMENT,
-					"The Bundle-RequiredExecutionEnvironment contains a comma-separated list of execution environments that must be present on the Service Platform.",
-					"Bundle-RequiredExecutionEnvironment: CDC-1.0/Foundation-1.0", Processor.join(Verifier.EES, ","),
+					"The " + BUNDLE_REQUIREDEXECUTIONENVIRONMENT + " contains a comma-separated list of execution environments that must be present on the Service Platform.",
+					BUNDLE_REQUIREDEXECUTIONENVIRONMENT + ": CDC-1.0/Foundation-1.0", Processor.join(Verifier.EES, ","),
 					Verifier.ANYPATTERN),
 
 			new Syntax(
 					BUNDLE_SYMBOLICNAME,
-					"The Bundle-SymbolicName header specifies a non-localizable name for this bundle. The bundle symbolic name together with a version must identify a  unique bundle. The bundle symbolic name should be based on the reverse  domain name convention",
-					"Bundle-SymbolicName: com.acme.foo.daffy;singleton:=true",
+					"The " + BUNDLE_SYMBOLICNAME + " header specifies a non-localizable name for this bundle. The bundle symbolic name together with a version must identify a unique bundle. The bundle symbolic name should be based on the reverse domain name convention.",
+					BUNDLE_SYMBOLICNAME + ": com.acme.foo.daffy;singleton:=true",
 					"${p}",
 					Verifier.SYMBOLICNAME,
 					new Syntax(
 							SINGLETON_DIRECTIVE,
-							" Indicates that the bundle can only have  a single version resolved.  A value of true indicates that the bundle is a singleton bundle. The default value is false. The Framework must resolve at most one  bundle when multiple versions of a singleton bundle with the same symbolic name are installed. Singleton bundles do not affect the resolution of non-singleton bundles with the same symbolic name.",
-							"false", "true,false", Verifier.TRUEORFALSEPATTERN),
+							"Indicates that the bundle can only have a single version resolved. A value of true indicates that the bundle is a singleton bundle. The default value is false. The Framework must resolve at most one bundle when multiple versions of a singleton bundle with the same symbolic name are installed. Singleton bundles do not affect the resolution of non-singleton bundles with the same symbolic name.",
+							SINGLETON_DIRECTIVE + "=false", "true,false", Verifier.TRUEORFALSEPATTERN),
 					new Syntax(
 							FRAGMENT_ATTACHMENT_DIRECTIVE,
-							"Defines how fragments are allowed to be attached, see the fragments in Fragment Bundles on page73. The following values are valid for this directive:",
+							"Defines how fragments are allowed to be attached, see the fragments in Fragment Bundles on page 73. The following values are valid for this directive:",
 							"", "always|never|resolve-time", Pattern.compile("always|never|resolve-time")), new Syntax(
 							BLUEPRINT_WAIT_FOR_DEPENDENCIES_ATTRIBUTE, "", "", "true,false",
 							Verifier.TRUEORFALSEPATTERN), new Syntax(BLUEPRINT_TIMEOUT_ATTRIBUTE, "", "",
@@ -136,199 +136,199 @@
 
 			new Syntax(
 					BUNDLE_UPDATELOCATION,
-					"The Bundle-UpdateLocation header specifies a URL where an update for this bundle should come from. If the bundle is updated, this location should be used, if present, to retrieve the updated JAR file.",
-					"Bundle-UpdateLocation: http://www.acme.com/Firewall/bundle.jar", null, Verifier.URLPATTERN),
+					"The " + BUNDLE_UPDATELOCATION + " header specifies a URL where an update for this bundle should come from. If the bundle is updated, this location should be used, if present, to retrieve the updated JAR file.",
+					BUNDLE_UPDATELOCATION + ": http://www.acme.com/Firewall/bundle.jar", null, Verifier.URLPATTERN),
 
 			new Syntax(BUNDLE_VENDOR,
-					"The Bundle-Vendor header contains a human-readable description of the bundle vendor. ",
-					"Bundle-Vendor: OSGi Alliance ", null, null),
+					"The " + BUNDLE_VENDOR + " header contains a human-readable description of the bundle vendor.",
+					BUNDLE_VENDOR + ": OSGi Alliance", null, null),
 
-			new Syntax(BUNDLE_VERSION, "The Bundle-Version header specifies the version of this bundle",
-					"Bundle-Version: 1.23.4.build200903221000", null, Verifier.VERSION),
+			new Syntax(BUNDLE_VERSION, "The " + BUNDLE_VERSION + " header specifies the version of this bundle.",
+					BUNDLE_VERSION + ": 1.23.4.build200903221000", null, Verifier.VERSION),
 
 			new Syntax(
 					DYNAMICIMPORT_PACKAGE,
-					"The DynamicImport-Package header contains a comma-separated list of package names that should be dynamically imported when needed.",
-					"DynamicImport-Package: com.acme.plugin.*", "", Verifier.WILDCARDNAMEPATTERN, version,
+					"The " + DYNAMICIMPORT_PACKAGE + " header contains a comma-separated list of package names that should be dynamically imported when needed.",
+					DYNAMICIMPORT_PACKAGE + ": com.acme.plugin.*", "", Verifier.WILDCARDNAMEPATTERN, version,
 					bundle_symbolic_name, bundle_version),
 
 			new Syntax(
 					EXPORT_PACKAGE,
-					"The Export-Package header contains a declaration of exported packages.",
-					"Export-Package: org.osgi.util.tracker;version=1.3",
+					"The " + EXPORT_PACKAGE + " header contains a declaration of exported packages.",
+					EXPORT_PACKAGE + ": org.osgi.util.tracker;version=1.3",
 					"${packages}",
 					null,
 					new Syntax(
 							NO_IMPORT_DIRECTIVE,
-							"By default, bnd makes all exports also imports. Adding a -noimport to an exported package will make it export only",
-							"-noimport:=true", "true,false", Verifier.TRUEORFALSEPATTERN),
+							"By default, bnd makes all exports also imports. Adding a " + NO_IMPORT_DIRECTIVE + " to an exported package will make it export only.",
+							NO_IMPORT_DIRECTIVE + "=true", "true,false", Verifier.TRUEORFALSEPATTERN),
 					new Syntax(
 							USES_DIRECTIVE,
-							"Calculated by bnd: It is a comma-separated list of package names that are used by the exported package",
+							"Calculated by bnd: It is a comma-separated list of package names that are used by the exported package.",
 							"Is calculated by bnd", null, null),
 					new Syntax(
 							MANDATORY_DIRECTIVE,
-							"A comma-separated list of attribute names. Note that the use of a comma in the value requires it to be enclosed in double quotes. A bundle importing the package must specify the mandatory attributes, with a value that matches, to resolve to the exported package",
-							"mandatory=\"bar,foo\"", null, null), new Syntax(INCLUDE_DIRECTIVE,
-							"A comma-separated list of class names that must be visible to an importer",
-							"include:=\"Qux*\"", null, null), new Syntax(EXCLUDE_DIRECTIVE,
-							"A comma-separated list of class names that must not be visible to an importer",
-							"exclude:=\"QuxImpl*,BarImpl\"", null, Verifier.WILDCARDNAMEPATTERN), new Syntax(
-							IMPORT_DIRECTIVE, "Experimental", "", null, null)
+							"A comma-separated list of attribute names. Note that the use of a comma in the value requires it to be enclosed in double quotes. A bundle importing the package must specify the mandatory attributes, with a value that matches, to resolve to the exported package.",
+							MANDATORY_DIRECTIVE + "=\"bar,foo\"", null, null), new Syntax(INCLUDE_DIRECTIVE,
+							"A comma-separated list of class names that must be visible to an importer.",
+							INCLUDE_DIRECTIVE + "=\"Qux*\"", null, null), new Syntax(EXCLUDE_DIRECTIVE,
+							"A comma-separated list of class names that must not be visible to an importer.",
+							EXCLUDE_DIRECTIVE + "=\"QuxImpl*,BarImpl\"", null, Verifier.WILDCARDNAMEPATTERN), new Syntax(
+							IMPORT_DIRECTIVE, "Experimental.", "", null, null)
 
 			),
-			new Syntax(EXPORT_SERVICE, "Deprecated", "Export-Service: org.osgi.service.log.LogService ",
+			new Syntax(EXPORT_SERVICE, "Deprecated.", EXPORT_SERVICE + ": org.osgi.service.log.LogService",
 					"${classes;implementing;*}", null),
 			new Syntax(
 					FRAGMENT_HOST,
-					"The Fragment-Host header defines the host bundle for this fragment.",
-					"Fragment-Host: org.eclipse.swt; bundle-version=\"[3.0.0,4.0.0)\"",
+					"The " + FRAGMENT_HOST + " header defines the host bundle for this fragment.",
+					FRAGMENT_HOST + ": org.eclipse.swt; bundle-version=\"[3.0.0,4.0.0)\"",
 					null,
 					null,
 					new Syntax(
 							EXTENSION_DIRECTIVE,
-							" Indicates this extension is a system or boot class path extension. It is only applicable when the Fragment-Host is the System Bundle",
-							"extension:=framework", "framework,bootclasspath", Pattern
+							"Indicates this extension is a system or boot class path extension. It is only applicable when the Fragment-Host is the System Bundle.",
+							EXTENSION_DIRECTIVE + "=framework", "framework,bootclasspath", Pattern
 									.compile("framework|bootclasspath")), bundle_version),
 			new Syntax(
 					IMPORT_PACKAGE,
-					"This header is normally calculated by bnd, however, you can decorate packages or skip packages. The Import-Package header declares the imported packages for this bundle",
-					"Import-Package: !com.exotic.*, com.acme.foo;vendor=ACME, *",
+					"The " + IMPORT_PACKAGE + " header is normally calculated by bnd, however, you can decorate packages or skip packages. The header declares the imported packages for this bundle.",
+					IMPORT_PACKAGE + ": !com.exotic.*, com.acme.foo;vendor=ACME, *",
 					"${exported_packages}",
 					Verifier.WILDCARDNAMEPATTERN,
 					new Syntax(REMOVE_ATTRIBUTE_DIRECTIVE,
-							"Remove the given attributes from matching imported packages", "-remove-attribute:=foo.*",
+							"Remove the given attributes from matching imported packages.", REMOVE_ATTRIBUTE_DIRECTIVE + "=foo.*",
 							null, Verifier.WILDCARDNAMEPATTERN),
 					new Syntax(
 							RESOLUTION_DIRECTIVE,
-							"Indicates that the packages must be resolved if the value is mandatory, which is the default. If mandatory packages cannot be resolved, then the bundle must fail to resolve. A value of optional indicates that the packages are optional",
-							"resolution:=optional", "mandatory,optional", Pattern.compile("mandatory|optional")
+							"Indicates that the packages must be resolved if the value is mandatory, which is the default. If mandatory packages cannot be resolved, then the bundle must fail to resolve. A value of optional indicates that the packages are optional.",
+							RESOLUTION_DIRECTIVE + "=optional", "mandatory,optional", Pattern.compile("mandatory|optional")
 
 					), version, bundle_symbolic_name, bundle_version),
 
 			new Syntax(
 					REQUIRE_BUNDLE,
-					"The Require-Bundle header specifies the required exports from another bundle.",
-					"Require-Bundle: com.acme.chess",
+					"The " + REQUIRE_BUNDLE + " header specifies the required exports from another bundle.",
+					REQUIRE_BUNDLE + ": com.acme.chess",
 					null,
 					Verifier.WILDCARDNAMEPATTERN,
 
 					new Syntax(
 							VISIBILITY_DIRECTIVE,
-							" If the value is private (Default), then all visible packages from the required bundles are not re-exported. If the value is reexport then bundles that require this bundle will transitively have access to these required bundle’s exported packages.",
-							"visibility:=private", "private,reexport", Pattern.compile("private|reexport")),
+							"If the value is private (Default), then all visible packages from the required bundles are not re-exported. If the value is reexport then bundles that require this bundle will transitively have access to these required bundle’s exported packages.",
+							VISIBILITY_DIRECTIVE + "=private", "private,reexport", Pattern.compile("private|reexport")),
 
 					new Syntax(
 							RESOLUTION_DIRECTIVE,
 							"If the value is mandatory (default) then the required bundle must exist for this bundle to resolve. If the value is optional, the bundle will resolve even if the required bundle does not exist.",
-							"resolution:=optional", "mandatory,optional", Pattern.compile("mandatory|optional")),
+							RESOLUTION_DIRECTIVE + "=optional", "mandatory,optional", Pattern.compile("mandatory|optional")),
 
 					new Syntax(
 							SPLIT_PACKAGE_DIRECTIVE,
-							"Indicates how an imported package should be merged when it is split between different exporters. The default is merge-first with warning",
-							"-split-package:=merge-first", "merge-first,merge-last,error,first", Pattern
+							"Indicates how an imported package should be merged when it is split between different exporters. The default is merge-first with warning.",
+							SPLIT_PACKAGE_DIRECTIVE + "=merge-first", "merge-first,merge-last,error,first", Pattern
 									.compile("merge-first|merge-last|error|first")), bundle_version
 
 			),
 			new Syntax(BUILDPATH,
-					"Provides the class path for building the jar. The entries are references to the repository",
-					"-buildpath=osgi;version=4.1", "${repo;bsns}", Verifier.SYMBOLICNAME, path_version),
+					"Provides the class path for building the jar. The entries are references to the repository.",
+					BUILDPATH + "=osgi;version=4.1", "${repo;bsns}", Verifier.SYMBOLICNAME, path_version),
 			new Syntax(BUMPPOLICY, "Sets the version bump policy. This is a parameter to the ${version} macro.",
-					"-bumppolicy==+0", "==+,=+0,+00", Pattern.compile("[=+-0][=+-0][=+-0]")),
+					BUMPPOLICY + "==+0", "==+,=+0,+00", Pattern.compile("[=+-0][=+-0][=+-0]")),
 
 			new Syntax(CONDUIT,
-					"Allows a bnd file to point to files which will be returned when the bnd file is build",
-					"-conduit= jar/osgi.jar", null, null),
+					"Allows a bnd file to point to files which will be returned when the bnd file is build.",
+					CONDUIT + "= jar/osgi.jar", null, null),
 
 			new Syntax(
 					DEPENDSON,
-					"List of project names that this project directly depends on. These projects are always build ahead of this project",
-					"-dependson=org.acme.cm", "${projects}", null),
+					"List of project names that this project directly depends on. These projects are always build ahead of this project.",
+					DEPENDSON + "=org.acme.cm", "${projects}", null),
 
-			new Syntax(DEPLOYREPO, "Specifies to which repo the project should be deployed.", "-deployrepo=cnf",
+			new Syntax(DEPLOYREPO, "Specifies to which repo the project should be deployed.", DEPLOYREPO + "=cnf",
 					"${repos}", null),
 
 			new Syntax(DONOTCOPY,
-					"Regular expression for names of files and directories that should not be copied when discovered",
-					"-donotcopy=(CVS|\\.svn)", null, null),
+					"Regular expression for names of files and directories that should not be copied when discovered.",
+					DONOTCOPY + "=(CVS|\\.svn)", null, null),
 
 			new Syntax(
 					EXPORT_CONTENTS,
-					"Build the JAR in the normal way but use this header for the Export-Package header manifest generation, same format",
-					"-exportcontents=!*impl*,*;version=3.0", null, null),
+					"Build the JAR in the normal way but use this header for the " + EXPORT_PACKAGE + " header manifest generation, same format.",
+					EXPORT_CONTENTS + "=!*impl*,*;version=3.0", null, null),
 
-			new Syntax(FAIL_OK, "Return with an ok status (0) even if the build generates errors", "-failok=true",
+			new Syntax(FAIL_OK, "Return with an ok status (0) even if the build generates errors.", FAIL_OK + "=true",
 					"true,false", Verifier.TRUEORFALSEPATTERN),
 
 			new Syntax(
 					INCLUDE,
-					"Include files. If an entry starts with '-', it does not have to exist. If it starts with '~', it must not overwrite any existing properties",
-					"-include: -${java.user}/.bnd", null, null),
+					"Include files. If an entry starts with '-', it does not have to exist. If it starts with '~', it must not overwrite any existing properties.",
+					INCLUDE + ": -${java.user}/.bnd", null, null),
 
 			new Syntax(
 					INCLUDERESOURCE,
-					"Include resources from the file system. You can specify a directory, or file. All files are copied to the root, unless a destination directory is indicated",
-					"-includeresource: lib=jar", null, null),
+					"Include resources from the file system. You can specify a directory, or file. All files are copied to the root, unless a destination directory is indicated.",
+					INCLUDERESOURCE + ": lib=jar", null, null),
 
 			new Syntax(
 					MAKE,
 					"Set patterns for make plugins. These patterns are used to find a plugin that can make a resource that can not be found.",
-					"-make: (*).jar;type=bnd;  recipe=\"bnd/$1.bnd\"", null, null, new Syntax("type",
-							"Type name for plugin", "type=bnd", "bnd", null), new Syntax("recipe",
-							"Recipe for the plugin, can use back references", "recipe=\"bnd/$1.bnd\"", "bnd", null)),
+					MAKE + ": (*).jar;type=bnd; recipe=\"bnd/$1.bnd\"", null, null, new Syntax("type",
+							"Type name for plugin.", "type=bnd", "bnd", null), new Syntax("recipe",
+							"Recipe for the plugin, can use back references.", "recipe=\"bnd/$1.bnd\"", "bnd", null)),
 
-			new Syntax(MANIFEST, "Directly include a manifest, do not use the calculated manifest",
-					"-manifest = META-INF/MANIFEST.MF", null, null),
+			new Syntax(MANIFEST, "Directly include a manifest, do not use the calculated manifest.",
+					MANIFEST + "=META-INF/MANIFEST.MF", null, null),
 
-			new Syntax(NOEXTRAHEADERS, "Do not generate housekeeping headers", "-noextraheaders", "true,false",
+			new Syntax(NOEXTRAHEADERS, "Do not generate housekeeping headers.", NOEXTRAHEADERS + "=true", "true,false",
 					Verifier.TRUEORFALSEPATTERN),
 
-			new Syntax(NOUSES, "Do not calculate the uses: directive on exports", "-nouses=true", "true,false",
+			new Syntax(NOUSES, "Do not calculate the " + USES_DIRECTIVE + " directive on exports.", NOUSES + "=true", "true,false",
 					Verifier.TRUEORFALSEPATTERN),
 
-			new Syntax(PEDANTIC, "Warn about things that are not really wrong but still not right", "-nope=true",
+			new Syntax(PEDANTIC, "Warn about things that are not really wrong but still not right.", PEDANTIC + "=true",
 					"true,false", Verifier.TRUEORFALSEPATTERN),
 
-			new Syntax(PLUGIN, "Define the plugins",
-					"-plugin=aQute.lib.spring.SpringComponent,aQute.lib.deployer.FileRepo;location=${repo}", null, null),
+			new Syntax(PLUGIN, "Define the plugins.",
+					PLUGIN + "=aQute.lib.spring.SpringComponent,aQute.lib.deployer.FileRepo;location=${repo}", null, null),
 
-			new Syntax(SERVICE_COMPONENT, "The header for Declarative Services",
-					"Service-Component=com.acme.Foo?;activate='start'", null, null),
+			new Syntax(SERVICE_COMPONENT, "The header for Declarative Services.",
+					SERVICE_COMPONENT + "=com.acme.Foo?;activate='start'", null, null),
 
-			new Syntax(POM, "Generate a maven pom", "-pom=true", "true,false", Verifier.TRUEORFALSEPATTERN),
+			new Syntax(POM, "Generate a maven pom.", POM + "=true", "true,false", Verifier.TRUEORFALSEPATTERN),
 
-			new Syntax(RELEASEREPO, "Specifies to which repo the project should be released.", "-releaserepo=cnf",
+			new Syntax(RELEASEREPO, "Specifies to which repo the project should be released.", RELEASEREPO + "=cnf",
 					"${repos}", null),
 
-			new Syntax(REMOVEHEADERS, "Remove all headers that match the regular expressions",
-					"-removeheaders=FOO_.*,Proprietary", null, null),
+			new Syntax(REMOVEHEADERS, "Remove all headers that match the regular expressions.",
+					REMOVEHEADERS + "=FOO_.*,Proprietary", null, null),
 			new Syntax(
 					RESOURCEONLY,
-					"Normally bnd warns when the JAR does not contain any classes, this option suppresses this warning",
-					"-resourceonly=true", "true,false", Verifier.TRUEORFALSEPATTERN),
-			new Syntax(SOURCES, "Include sources in the jar", "-sources=true", "true,false",
+					"Normally bnd warns when the JAR does not contain any classes, this option suppresses this warning.",
+					RESOURCEONLY + "=true", "true,false", Verifier.TRUEORFALSEPATTERN),
+			new Syntax(SOURCES, "Include sources in the jar.", SOURCES + "=true", "true,false",
 					Verifier.TRUEORFALSEPATTERN),
-			new Syntax(SOURCEPATH, "List of directory names that used to source sources for -sources",
-					"-sourcepath:= src, test", null, null),
+			new Syntax(SOURCEPATH, "List of directory names that used to source sources for " + SOURCES + ".",
+					SOURCEPATH + ":= src, test", null, null),
 			new Syntax(
 					SUB,
-					"Build a set of bnd files that use this bnd file as a basis. The list of bnd file can be specified with wildcards",
-					"-sub=com.acme.*.bnd", null, null),
-			new Syntax(RUNPROPERTIES, "Properties that are set as system properties before the framework is started",
-					"-runproperties= foo=3, bar=4", null, null),
-			new Syntax(RUNSYSTEMPACKAGES, "Add additional system packages to a framework run",
-					"-runsystempackages=com.acme.foo,javax.management", null, null),
+					"Build a set of bnd files that use this bnd file as a basis. The list of bnd file can be specified with wildcards.",
+					SUB + "=com.acme.*.bnd", null, null),
+			new Syntax(RUNPROPERTIES, "Properties that are set as system properties before the framework is started.",
+					RUNPROPERTIES + "= foo=3, bar=4", null, null),
+			new Syntax(RUNSYSTEMPACKAGES, "Add additional system packages to a framework run.",
+					RUNSYSTEMPACKAGES + "=com.acme.foo,javax.management", null, null),
 			new Syntax(
 					RUNBUNDLES,
-					"Add additional bundles, specified with their bsn and version like in -buildpath, that are started before the project is run",
-					"-runbundles=osgi;version=\"[4.1,4.2)\", junit.junit, com.acme.foo;version=project", null,
+					"Add additional bundles, specified with their bsn and version like in " + BUILDPATH + ", that are started before the project is run.",
+					RUNBUNDLES + "=osgi;version=\"[4.1,4.2)\", junit.junit, com.acme.foo;version=project", null,
 					Verifier.SYMBOLICNAME, path_version),
-			new Syntax(RUNPATH, "Additional JARs for the VM path, should include the framework",
-					"-runpath=org.eclipse.osgi;version=3.5", null, null, path_version),
+			new Syntax(RUNPATH, "Additional JARs for the VM path, should include the framework.",
+					RUNPATH + "=org.eclipse.osgi;version=3.5", null, null, path_version),
 			new Syntax(
 					RUNVM,
-					"Additional arguments for the VM invokation. Keys that start with a - are added as options, otherwise they are treated as -D properties for the VM",
-					"-runvm=-Xmax=30", null, null)
+					"Additional arguments for the VM invokation. Keys that start with a - are added as options, otherwise they are treated as -D properties for the VM.",
+					RUNVM + "=-Xmax=30", null, null)
 																	};
 
 	public final static Map<String,Syntax>	HELP					= new HashMap<String,Syntax>();
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java b/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
index 14cba4b..76fac28 100644
--- a/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
+++ b/bundleplugin/src/main/java/aQute/bnd/make/calltree/CalltreeResource.java
@@ -86,21 +86,21 @@
 				COMPARATOR);
 
 		ClassDataCollector cd = new ClassDataCollector() {
-			Clazz.MethodDef	source;
+//			Clazz.MethodDef	source;
 
 			// Before a method is parsed
 			@Override
 			public void method(Clazz.MethodDef source) {
-				this.source = source;
+//				this.source = source;
 				xref(using, source, null);
 				xref(usedby, source, null);
 			}
 
 			// For any reference in the previous method.
-			public void reference(Clazz.MethodDef reference) {
-				xref(using, source, reference);
-				xref(usedby, reference, source);
-			}
+//			public void reference(Clazz.MethodDef reference) {
+//				xref(using, source, reference);
+//				xref(usedby, reference, source);
+//			}
 		};
 		for (Clazz clazz : classes) {
 			clazz.parseClassFileWithCollector(cd);
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java b/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
index 5367c93..eaa5ddb 100644
--- a/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
+++ b/bundleplugin/src/main/java/aQute/bnd/make/component/ServiceComponent.java
@@ -1,9 +1,7 @@
 package aQute.bnd.make.component;
 
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.Collection;
-import java.util.Map;
+import java.io.*;
+import java.util.*;
 import java.util.Map.Entry;
 
 import aQute.bnd.annotation.component.*;
@@ -14,7 +12,7 @@
 import aQute.bnd.osgi.Clazz.QUERY;
 import aQute.bnd.osgi.Descriptors.TypeRef;
 import aQute.bnd.service.*;
-import aQute.lib.tag.Tag;
+import aQute.lib.tag.*;
 
 /**
  * This class is an analyzer plugin. It looks at the properties and tries to
diff --git a/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java b/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
index 2878248..672c5cd 100644
--- a/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
+++ b/bundleplugin/src/main/java/aQute/bnd/make/coverage/Coverage.java
@@ -35,7 +35,7 @@
 			throws Exception {
 		for (final Clazz clazz : source) {
 			clazz.parseClassFileWithCollector(new ClassDataCollector() {
-				MethodDef	source;
+//				MethodDef	source;
 
 				@Override
 				public void implementsInterfaces(TypeRef names[]) {
@@ -56,15 +56,16 @@
 				// Method definitions
 				@Override
 				public void method(MethodDef source) {
-					this.source = source;
+//					this.source = source;
 				}
 
-				public void reference(MethodDef reference) {
-					List<MethodDef> references = catalog.get(reference);
-					if (references != null) {
-						references.add(source);
-					}
-				}
+				// TODO need to use different reference method
+//				public void reference(MethodDef reference) {
+//					List<MethodDef> references = catalog.get(reference);
+//					if (references != null) {
+//						references.add(source);
+//					}
+//				}
 			});
 		}
 	}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
index b1bdc42..bf5e15c 100644
--- a/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
@@ -13,9 +13,9 @@
 import aQute.bnd.maven.support.Pom.Scope;
 import aQute.bnd.osgi.*;
 import aQute.bnd.osgi.Descriptors.PackageRef;
-import aQute.bnd.settings.*;
 import aQute.lib.collections.*;
 import aQute.lib.io.*;
+import aQute.lib.settings.*;
 import aQute.libg.command.*;
 
 public class MavenCommand extends Processor {
@@ -185,7 +185,7 @@
 		}
 
 		if (developers.isEmpty()) {
-			String email = settings.globalGet(Settings.EMAIL, null);
+			String email = settings.remove("email");
 			if (email == null)
 				error("No developer email set, you can set global default email with: bnd global email Peter.Kriens@aQute.biz");
 			else
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
index 33d21e0..d8d54e0 100644
--- a/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
@@ -7,6 +7,7 @@
 import aQute.bnd.osgi.*;
 import aQute.bnd.service.*;
 import aQute.bnd.version.*;
+import aQute.lib.collections.*;
 import aQute.service.reporter.*;
 
 public class MavenRepository implements RepositoryPlugin, Plugin, BsnToMavenPath {
@@ -114,10 +115,10 @@
 	}
 
 	public PutResult put(InputStream stream, PutOptions options) throws Exception {
-		throw new IllegalStateException("Maven does not support the put command");
+		throw new UnsupportedOperationException("Maven does not support the put command");
 	}
 
-	public List<Version> versions(String bsn) throws Exception {
+	public SortedSet<Version> versions(String bsn) throws Exception {
 
 		File files[] = get(bsn, null);
 		List<Version> versions = new ArrayList<Version>();
@@ -127,7 +128,10 @@
 			Version v = new Version(version);
 			versions.add(v);
 		}
-		return versions;
+		if ( versions.isEmpty())
+			return SortedList.empty();
+		
+		return new SortedList<Version>(versions);
 	}
 
 	public void setProperties(Map<String,String> map) {
@@ -201,4 +205,20 @@
 	public String getLocation() {
 		return root.toString();
 	}
+
+	public File get(String bsn, Version version, Map<String,String> properties, DownloadListener ... listeners) throws Exception {
+		File file = get(bsn, version.toString(), Strategy.EXACT, properties);
+		if ( file == null)
+			return null;
+		
+		for (DownloadListener l : listeners) {
+			try {
+				l.success(file);
+			}
+			catch (Exception e) {
+				reporter.exception(e, "Download listener for %s", file);
+			}
+		}
+		return file;
+	}
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
index 4609e32..345d3eb 100644
--- a/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
@@ -75,7 +75,7 @@
 		throw new UnsupportedOperationException("cannot do list");
 	}
 
-	public List<Version> versions(String bsn) throws Exception {
+	public SortedSet<Version> versions(String bsn) throws Exception {
 		throw new UnsupportedOperationException("cannot do versions");
 	}
 
@@ -126,4 +126,21 @@
 
 		return Arrays.toString(repositories);
 	}
+
+	public File get(String bsn, Version version, Map<String,String> properties, DownloadListener ... listeners) throws Exception {
+		File f= get(bsn, version.toString(), Strategy.EXACT, properties);
+		if ( f == null)
+			return null;
+		
+		for (DownloadListener l : listeners) {
+			try {
+				l.success(f);
+			}
+			catch (Exception e) {
+				reporter.exception(e, "Download listener for %s", f);
+			}
+		}
+		return f;
+	}
+
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Analyzer.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Analyzer.java
index 135f96c..860fe08 100755
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Analyzer.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Analyzer.java
@@ -698,6 +698,10 @@
 		return apiUses;
 	}
 
+	public Packages getClasspathExports() {
+		return classpathExports;
+	}
+
 	/**
 	 * Get the version for this bnd
 	 * 
@@ -2035,6 +2039,8 @@
 				typeName = "extends";
 			else if (typeName.equalsIgnoreCase("importing"))
 				typeName = "imports";
+			else if (typeName.equalsIgnoreCase("annotation"))
+				typeName = "annotated";
 			else if (typeName.equalsIgnoreCase("implementing"))
 				typeName = "implements";
 
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Annotation.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Annotation.java
index 0383962..aca0a11 100644
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Annotation.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Annotation.java
@@ -62,8 +62,15 @@
 
 	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);
+		try {
+			Class<T> c = (Class<T>) getClass().getClassLoader().loadClass(cname);
+			return getAnnotation(c);
+		}
+		catch (ClassNotFoundException e) {
+		}
+		catch (NoClassDefFoundError e) {
+		}
+		return null;
 	}
 
 	public <T extends java.lang.annotation.Annotation> T getAnnotation(Class<T> c) throws Exception {
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Builder.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Builder.java
index 2ee603a..b5ed9be 100755
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Builder.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Builder.java
@@ -9,7 +9,6 @@
 
 import aQute.bnd.component.*;
 import aQute.bnd.differ.*;
-import aQute.bnd.differ.Baseline.Info;
 import aQute.bnd.header.*;
 import aQute.bnd.make.*;
 import aQute.bnd.make.component.*;
@@ -18,7 +17,6 @@
 import aQute.bnd.osgi.Descriptors.PackageRef;
 import aQute.bnd.osgi.Descriptors.TypeRef;
 import aQute.bnd.service.*;
-import aQute.bnd.service.RepositoryPlugin.Strategy;
 import aQute.bnd.service.diff.*;
 import aQute.lib.collections.*;
 import aQute.libg.generics.*;
@@ -309,8 +307,10 @@
 				}
 			}
 		}
-		if (jar.getDirectories().size() == 0)
+		if (jar.getDirectories().size() == 0) {
+			trace("extra dirs %s", jar.getDirectories());
 			return null;
+		}
 		return jar;
 	}
 
@@ -860,6 +860,7 @@
 
 		if (!destination.contains("${@}")) {
 			cr = new CombinedResource();
+			cr.lastModified = lastModified;
 		}
 		trace("last modified requires %s", lastModified);
 
@@ -869,9 +870,10 @@
 				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 ( file.exists())
+					lastModified = Math.max(lastModified, file.lastModified());
+				
+				Resource r = new CommandResource(command, this, lastModified, getBase());
 
 				if (preprocess)
 					r = new PreprocessResource(this, r);
@@ -890,6 +892,8 @@
 		// to update the modified time.
 		if (cr != null)
 			jar.putResource(destination, cr);
+		
+		updateModified(lastModified, "Include-Resource: cmd");
 	}
 
 	private String doResourceDirectory(Jar jar, Map<String,String> extra, boolean preprocess, File sourceFile,
@@ -945,6 +949,10 @@
 				files.put(p, file);
 			}
 		}
+		if (fs.length == 0) {
+			File empty = new File(dir, Constants.EMPTY_HEADER);
+			files.put(appendPath(path, empty.getName()), empty);
+		}
 	}
 
 	private void noSuchFile(Jar jar, @SuppressWarnings("unused") String clause, Map<String,String> extra, String source, String destinationPath)
@@ -1065,6 +1073,8 @@
 				if (isTrue(extra.get(LIB_DIRECTIVE))) {
 					setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
 				}
+			} else if (from.getName().equals(Constants.EMPTY_HEADER)) {
+				jar.putResource(path, new EmbeddedResource(new byte[0], 0));
 			} else {
 				error("Input file does not exist: " + from);
 			}
@@ -1159,7 +1169,7 @@
 		Parameters subsMap = parseHeader(sub);
 		for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
 			File file = getFile(i.next());
-			if (file.isFile()) {
+			if (file.isFile() && !file.getName().startsWith(".")) {
 				builders.add(getSubBuilder(file));
 				i.remove();
 			}
@@ -1500,84 +1510,18 @@
 			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);
-
-		Jar other = getBaselineJar();
-		if (other == null) {
-			return;
-		}
-		Baseline baseline = new Baseline(this, differ);
-		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);
 		}
 	}
 
-	public Jar getBaselineJar() throws Exception {
+	/**
+	 * Base line against a previous version. Should be overridden in the ProjectBuilder where we have access to the repos
+	 * 
+	 * @throws Exception
+	 */
 
-		List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
-
-		Parameters diffs = parseHeader(getProperty("-baseline"));
-		File baselineFile = null;
-		if (diffs.isEmpty()) {
-			String repoName = getProperty("-baseline-repo");
-			if (repoName == null) {
-				return null;
-			}
-			for (RepositoryPlugin repo : repos) {
-				if (repoName.equals(repo.getName())) {
-					baselineFile = repo.get(getBsn(), null, Strategy.HIGHEST, null);
-					break;
-				}
-			}
-		} else {
-
-			String bsn = null;
-			String version = null;
-			for (Entry<String,Attrs> entry : diffs.entrySet()) {
-				bsn = entry.getKey();
-				if ("@".equals(bsn)) {
-					bsn = getBsn();
-				}
-				version = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
-				break;
-			}
-	
-			for (RepositoryPlugin repo : repos) {
-				if (version == null) {
-					baselineFile = repo.get(bsn, null, Strategy.HIGHEST, null);
-				} else {
-					baselineFile = repo.get(bsn, version, Strategy.EXACT, null);
-				}
-				if (baselineFile != null) {
-					break;
-				}
-			}
-		}
-		if (baselineFile == null) {
-			return new Jar(".");
-		}
-		return new Jar(baselineFile);
-	}
+	protected void doBaseline(Jar dot) throws Exception {}
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/ClassDataCollector.java b/bundleplugin/src/main/java/aQute/bnd/osgi/ClassDataCollector.java
index c2daba7..17aa62e 100644
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/ClassDataCollector.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/ClassDataCollector.java
@@ -2,28 +2,28 @@
 
 import aQute.bnd.osgi.Descriptors.TypeRef;
 
+@SuppressWarnings("unused")
 public class ClassDataCollector {
-	public void classBegin(@SuppressWarnings("unused") int access, @SuppressWarnings("unused") TypeRef name) {}
+	public void classBegin(int access, TypeRef name) {}
 
 	public boolean classStart(int access, TypeRef className) {
 		classBegin(access, className);
 		return true;
 	}
 
-	public void extendsClass(@SuppressWarnings("unused") TypeRef zuper) throws Exception {}
+	public void extendsClass(TypeRef zuper) throws Exception {}
 
-	public void implementsInterfaces(@SuppressWarnings("unused") TypeRef[] interfaces) throws Exception {}
+	public void implementsInterfaces(TypeRef[] interfaces) throws Exception {}
 
-	public void addReference(@SuppressWarnings("unused") TypeRef ref) {}
+	public void addReference(TypeRef ref) {}
 
-	public void annotation(@SuppressWarnings("unused") Annotation annotation) {}
+	public void annotation(Annotation annotation) {}
 
-	public void parameter(@SuppressWarnings("unused") int p) {}
+	public void parameter(int p) {}
 
-	public void method(@SuppressWarnings("unused") Clazz.MethodDef defined) {}
+	public void method(Clazz.MethodDef defined) {}
 
-	public void field(@SuppressWarnings("unused") Clazz.FieldDef defined) {}
-
+	public void field(Clazz.FieldDef defined) {}
 
 	public void classEnd() throws Exception {}
 
@@ -58,27 +58,18 @@
 	 *            The access flags
 	 * @throws Exception
 	 */
-	public void innerClass(TypeRef innerClass, TypeRef outerClass, String innerName, @SuppressWarnings("unused") int innerClassAccessFlags)
+	public void innerClass(TypeRef innerClass, TypeRef outerClass, String innerName, int innerClassAccessFlags)
 			throws Exception {}
 
-	public void signature(@SuppressWarnings("unused") String signature) {}
+	public void signature(String signature) {}
 
-	public void constant(@SuppressWarnings("unused") Object object) {}
+	public void constant(Object object) {}
 
 	public void memberEnd() {}
 
-	public void version(@SuppressWarnings("unused") int minor, @SuppressWarnings("unused") int major) {
-		// TODO Auto-generated method stub
+	public void version(int minor, int major) {}
 
-	}
-
-	public void referenceMethod(@SuppressWarnings("unused")
-	int access, @SuppressWarnings("unused")
-	TypeRef className, @SuppressWarnings("unused")
-	String method, @SuppressWarnings("unused") String descriptor) {
-		// TODO Auto-generated method stub
-
-	}
+	public void referenceMethod(int access, TypeRef className, String method, String descriptor) {}
 
 	/**
 	 * A reference to a type from method or field. The modifiers indicate the
@@ -87,8 +78,6 @@
 	 * @param typeRef
 	 * @param modifiers
 	 */
-	public void referTo(TypeRef typeRef, int modifiers) {
-		
-	}
+	public void referTo(TypeRef typeRef, int modifiers) {}
 
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Clazz.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Clazz.java
index 1a325e6..efab9a3 100755
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Clazz.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Clazz.java
@@ -147,7 +147,7 @@
 	}
 
 	public abstract class Def {
-		
+
 		final int		access;
 		Set<TypeRef>	annotations;
 
@@ -220,9 +220,11 @@
 		public TypeRef getOwnerType() {
 			return className;
 		}
-		
+
 		public abstract String getName();
+
 		public abstract TypeRef getType();
+
 		public abstract TypeRef[] getPrototype();
 
 		public Object getClazz() {
@@ -255,7 +257,6 @@
 		public String getName() {
 			return name;
 		}
-		
 
 		@Override
 		public TypeRef getType() {
@@ -296,6 +297,7 @@
 		public TypeRef[] getPrototype() {
 			return null;
 		}
+
 		public String getSignature() {
 			return signature;
 		}
@@ -338,7 +340,6 @@
 		public boolean getImplements() {
 			return interf;
 		}
-		
 
 		@Override
 		public String getName() {
@@ -346,11 +347,12 @@
 				return "<implements>";
 			return "<extends>";
 		}
-		
+
 		@Override
 		public TypeRef getType() {
 			return type;
 		}
+
 		@Override
 		public TypeRef[] getPrototype() {
 			return null;
@@ -859,9 +861,9 @@
 			doSignature(in, member, access_flags);
 		else if ("ConstantValue".equals(attributeName))
 			doConstantValue(in);
-        else if ("Exceptions".equals(attributeName))
-             doExceptions(in, access_flags);
-        else {
+		else if ("Exceptions".equals(attributeName))
+			doExceptions(in, access_flags);
+		else {
 			if (attribute_length > 0x7FFFFFFF) {
 				throw new IllegalArgumentException("Attribute > 2Gb");
 			}
@@ -1006,8 +1008,8 @@
 				referTo(clazz, access_flags);
 			}
 		}
-	}	
-	       
+	}
+
 	/**
 	 * <pre>
 	 * Code_attribute {
@@ -1154,7 +1156,8 @@
 		}
 	}
 
-	private void doAnnotations(DataInputStream in, ElementType member, RetentionPolicy policy, int access_flags) throws IOException {
+	private void doAnnotations(DataInputStream in, ElementType member, RetentionPolicy policy, int access_flags)
+			throws IOException {
 		int num_annotations = in.readUnsignedShort(); // # of annotations
 		for (int a = 0; a < num_annotations; a++) {
 			if (cd == null)
@@ -1166,8 +1169,8 @@
 		}
 	}
 
-	private Annotation doAnnotation(DataInputStream in, ElementType member, RetentionPolicy policy, boolean collect, int access_flags)
-			throws IOException {
+	private Annotation doAnnotation(DataInputStream in, ElementType member, RetentionPolicy policy, boolean collect,
+			int access_flags) throws IOException {
 		int type_index = in.readUnsignedShort();
 		if (annotations == null)
 			annotations = new HashSet<TypeRef>();
@@ -1201,8 +1204,8 @@
 		return null;
 	}
 
-	private Object doElementValue(DataInputStream in, ElementType member, RetentionPolicy policy, boolean collect, int access_flags)
-			throws IOException {
+	private Object doElementValue(DataInputStream in, ElementType member, RetentionPolicy policy, boolean collect,
+			int access_flags) throws IOException {
 		char tag = (char) in.readUnsignedByte();
 		switch (tag) {
 			case 'B' : // Byte
@@ -1228,8 +1231,8 @@
 				if (policy == RetentionPolicy.RUNTIME) {
 					referTo(type_name_index, 0);
 					if (api != null && (Modifier.isPublic(access_flags) || Modifier.isProtected(access_flags))) {
-						 TypeRef name = analyzer.getTypeRef((String) pool[type_name_index]);
-						 api.add(name.getPackageRef());
+						TypeRef name = analyzer.getTypeRef((String) pool[type_name_index]);
+						api.add(name.getPackageRef());
 					}
 				}
 				int const_name_index = in.readUnsignedShort();
@@ -1240,8 +1243,8 @@
 				if (policy == RetentionPolicy.RUNTIME) {
 					referTo(class_info_index, 0);
 					if (api != null && (Modifier.isPublic(access_flags) || Modifier.isProtected(access_flags))) {
-						 TypeRef name = analyzer.getTypeRef((String) pool[class_info_index]);
-						 api.add(name.getPackageRef());
+						TypeRef name = analyzer.getTypeRef((String) pool[class_info_index]);
+						api.add(name.getPackageRef());
 					}
 				}
 				return pool[class_info_index];
@@ -1729,5 +1732,4 @@
 	public Clazz.TypeDef getImplements(TypeRef type) {
 		return new TypeDef(type, true);
 	}
-
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/CommandResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/CommandResource.java
index eb66635..0e73945 100644
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/CommandResource.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/CommandResource.java
@@ -8,11 +8,13 @@
 	final long		lastModified;
 	final Builder	domain;
 	final String	command;
+	final File wd;
 
-	public CommandResource(String command, Builder domain, long lastModified) {
+	public CommandResource(String command, Builder domain, long lastModified, File wd) {
 		this.lastModified = lastModified;
 		this.domain = domain;
 		this.command = command;
+		this.wd = wd;
 	}
 
 	@Override
@@ -22,6 +24,7 @@
 		try {
 			domain.trace("executing command %s", command);
 			Command cmd = new Command("sh");
+			cmd.setCwd(wd);
 			cmd.inherit();
 			String oldpath = cmd.var("PATH");
 
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Constants.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Constants.java
index 34d60c6..4319538 100644
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Constants.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Constants.java
@@ -69,6 +69,8 @@
 			PROVIDE_CAPABILITY, BUNDLE_ICON
 																				};
 
+	String							BASELINE									= "-baseline";
+	String							BASELINEREPO								= "-baselinerepo";
 	String							BUILDPATH									= "-buildpath";
 	String							BUILDPACKAGES								= "-buildpackages";
 	String							BUMPPOLICY									= "-bumppolicy";
@@ -90,6 +92,7 @@
 	String							MAKE										= "-make";
 	String							METATYPE									= "-metatype";
 	String							MANIFEST									= "-manifest";
+	String							PROFILE										= "-profile";
 	String							SAVEMANIFEST								= "-savemanifest";
 	String							NAMESECTION									= "-namesection";
 	String							NODEFAULTVERSION							= "-nodefaultversion";
@@ -130,6 +133,7 @@
 	String							RUNVM										= "-runvm";
 	String							RUNTRACE									= "-runtrace";
 	String							RUNFRAMEWORK								= "-runframework";
+	String							RUNFW										= "-runfw";
 	String							RUNTIMEOUT									= "-runtimeout";
 	String							SNAPSHOT									= "-snapshot";
 	String							RUNFRAMEWORK_SERVICES						= "services";
@@ -153,12 +157,12 @@
 	String							OUTPUT										= "-output";
 
 	String							options[]									= {
-			BUILDPATH, BUMPPOLICY, CONDUIT, CLASSPATH, CONSUMER_POLICY, DEPENDSON, DONOTCOPY, EXPORT_CONTENTS, FAIL_OK,
+			BASELINE, 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, DSANNOTATIONS, EXPERIMENTS
+			VERBOSE, NOMANIFEST, DEPLOYREPO, RELEASEREPO, SAVEMANIFEST, RUNVM, WAB, WABLIB, RUNFRAMEWORK, RUNFW, RUNTRACE,
+			TESTCONTINUOUS, SNAPSHOT, NAMESECTION, DIGESTS, DSANNOTATIONS, EXPERIMENTS, BASELINE, BASELINEREPO, PROFILE
 																				};
 
 	// Ignore bundle specific headers. These bundles do not make
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Jar.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Jar.java
index 93c55a1..9ee4048 100755
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Jar.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Jar.java
@@ -515,6 +515,8 @@
 			return;
 		try {
 			createDirectories(directories, jout, path);
+			if (path.endsWith(Constants.EMPTY_HEADER))
+				return;
 			ZipEntry ze = new ZipEntry(path);
 			ze.setMethod(ZipEntry.DEFLATED);
 			long lastModified = resource.lastModified();
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Macro.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Macro.java
index d706596..bfd0621 100755
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Macro.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Macro.java
@@ -229,7 +229,7 @@
 			}
 			catch (InvocationTargetException e) {
 				if (e.getCause() instanceof IllegalArgumentException) {
-					domain.error("%s, for cmd: %s, arguments; %s", e.getMessage(), method, Arrays.toString(args));
+					domain.error("%s, for cmd: %s, arguments; %s", e.getCause().getMessage(), method, Arrays.toString(args));
 				} else {
 					domain.warning("Exception in replace: %s", e.getCause());
 					e.getCause().printStackTrace();
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
index 5958a9d..e30ddcd 100755
--- a/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
@@ -38,6 +38,7 @@
 	private File					base			= new File("").getAbsoluteFile();
 
 	Properties						properties;
+	String							profile;
 	private Macro					replacer;
 	private long					lastModified;
 	private File					propertiesFile;
@@ -138,7 +139,7 @@
 	}
 
 	public void progress(float progress, String format, Object... args) {
-		format = String.format("[%2d] %s", (int)progress, format);
+		format = String.format("[%2d] %s", (int) progress, format);
 		trace(format, args);
 	}
 
@@ -354,8 +355,8 @@
 					Class< ? > c = loader.loadClass(key);
 					Object plugin = c.newInstance();
 					customize(plugin, entry.getValue());
-					if ( plugin instanceof Closeable){
-						addClose((Closeable)plugin);
+					if (plugin instanceof Closeable) {
+						addClose((Closeable) plugin);
 					}
 					list.add(plugin);
 				}
@@ -470,7 +471,8 @@
 		toBeClosed.clear();
 	}
 
-	public String _basedir(@SuppressWarnings("unused") String args[]) {
+	public String _basedir(@SuppressWarnings("unused")
+	String args[]) {
 		if (base == null)
 			throw new IllegalArgumentException("No base dir set");
 
@@ -656,7 +658,8 @@
 
 	public boolean refresh() {
 		plugins = null; // We always refresh our plugins
-
+		
+		
 		if (propertiesFile == null)
 			return false;
 
@@ -670,6 +673,8 @@
 			}
 		}
 
+		profile = getProperty(PROFILE); // Used in property access
+		
 		if (changed) {
 			forceRefresh();
 			return true;
@@ -762,6 +767,7 @@
 	 * @return
 	 */
 	public String getProperty(String key, String deflt) {
+
 		String value = null;
 
 		Instruction ins = new Instruction(key);
@@ -789,6 +795,8 @@
 
 		Processor source = this;
 
+		// Use the key as is first, if found ok
+
 		if (filter != null && filter.contains(key)) {
 			value = (String) getProperties().get(key);
 		} else {
@@ -801,6 +809,26 @@
 			}
 		}
 
+		// Check if we found a value, if not, try to prefix
+		// it with a profile if found and search again. profiles
+		// are a simple name that is prefixed like [profile]. This
+		// allows different variables to be used in different profiles.
+
+		if (value == null && profile != null) {
+			String pkey = "[" + profile + "]" + key;
+			if (filter != null && filter.contains(key)) {
+				value = (String) getProperties().get(pkey);
+			} else {
+				while (source != null) {
+					value = (String) source.getProperties().get(pkey);
+					if (value != null)
+						break;
+
+					source = source.getParent();
+				}
+			}
+		}
+
 		if (value != null)
 			return getReplacer().process(value, source);
 		else if (deflt != null)
@@ -876,8 +904,8 @@
 		return printClauses(exports, false);
 	}
 
-	public static String printClauses(Map< ? , ? extends Map< ? , ? >> exports, @SuppressWarnings("unused") boolean checkMultipleVersions)
-			throws IOException {
+	public static String printClauses(Map< ? , ? extends Map< ? , ? >> exports, @SuppressWarnings("unused")
+	boolean checkMultipleVersions) throws IOException {
 		StringBuilder sb = new StringBuilder();
 		String del = "";
 		for (Entry< ? , ? extends Map< ? , ? >> entry : exports.entrySet()) {
@@ -977,7 +1005,8 @@
 		return result;
 	}
 
-	public boolean updateModified(long time, @SuppressWarnings("unused") String reason) {
+	public boolean updateModified(long time, @SuppressWarnings("unused")
+	String reason) {
 		if (time > lastModified) {
 			lastModified = time;
 			return true;
diff --git a/bundleplugin/src/main/java/aQute/bnd/repo/eclipse/EclipseRepo.java b/bundleplugin/src/main/java/aQute/bnd/repo/eclipse/EclipseRepo.java
deleted file mode 100644
index d7817ad..0000000
--- a/bundleplugin/src/main/java/aQute/bnd/repo/eclipse/EclipseRepo.java
+++ /dev/null
@@ -1,206 +0,0 @@
-package aQute.bnd.repo.eclipse;
-
-import java.io.*;
-import java.util.*;
-import java.util.Map.Entry;
-import java.util.jar.*;
-
-import aQute.bnd.header.*;
-import aQute.bnd.osgi.*;
-import aQute.bnd.service.*;
-import aQute.bnd.version.*;
-import aQute.lib.io.*;
-import aQute.libg.generics.*;
-import aQute.service.reporter.*;
-
-public class EclipseRepo implements Plugin, RepositoryPlugin {
-	File						root;
-	Reporter					reporter;
-	String						name;
-	Parameters					index;
-
-	public final static String	LOCATION	= "location";
-	public final static String	NAME		= "name";
-
-	public void setProperties(Map<String,String> map) {
-		String location = map.get(LOCATION);
-		if (location == null)
-			throw new IllegalArgumentException("Location muse be set on a EclipseRepo plugin");
-
-		root = new File(location);
-		if (!root.isDirectory())
-			throw new IllegalArgumentException("Repository is not a valid directory " + root);
-
-		if (!new File(root, "plugins").isDirectory())
-			throw new IllegalArgumentException("Repository is not a valid directory (no plugins directory)" + root);
-
-		name = map.get(NAME);
-
-		try {
-			index = buildIndex();
-		}
-		catch (Exception e) {
-			throw new RuntimeException("Could not build index for eclipse repo: " + root);
-		}
-	}
-
-	Parameters buildIndex() throws Exception {
-		File index = new File(root, "bnd.index").getAbsoluteFile();
-		File[] plugins = new File(root, "plugins").listFiles();
-
-		for (File f : plugins) {
-			f = f.getAbsoluteFile();
-			if (f.isFile()) {
-				if (f.lastModified() > index.lastModified()) {
-
-					Parameters map = buildIndex(plugins);
-					write(index, map);
-					return map;
-				}
-			}
-		}
-
-		String s = read(index);
-		return Processor.parseHeader(s, null);
-	}
-
-	private String read(File index) throws Exception {
-		if (index.isFile()) {
-			BufferedReader fr = IO.reader(index);
-			StringBuilder sb = new StringBuilder();
-
-			try {
-				String s = fr.readLine();
-				while (s != null) {
-					sb.append(s);
-					s = fr.readLine();
-				}
-			}
-			finally {
-				fr.close();
-			}
-		}
-		return null;
-	}
-
-	private void write(File index, Map<String, ? extends Map<String,String>> map) throws Exception {
-		String s = Processor.printClauses(map);
-		File ip = index.getParentFile();
-		if (!ip.exists() && !ip.mkdirs()) {
-			throw new IOException("Could not create directory " + ip);
-		}
-		PrintWriter fw = IO.writer(index);
-		try {
-			fw.write(s);
-		}
-		finally {
-			fw.close();
-		}
-	}
-
-	private Parameters buildIndex(File[] plugins) {
-		Parameters map = new Parameters();
-		for (File plugin : plugins) {
-			try {
-				Jar jar = new Jar(plugin);
-				Manifest manifest = jar.getManifest();
-				String bsn = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-				String version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-
-				if (bsn != null) {
-					if (version == null)
-						version = "0";
-
-					Map<String,String> instance = map.get(bsn);
-					if (instance == null) {
-						instance = Create.map();
-					}
-					instance.put(version, plugin.getAbsolutePath());
-				}
-			}
-			catch (Exception e) {
-				// Ignore exceptions in the plugins dir.
-			}
-		}
-		return map;
-	}
-
-	public void setReporter(Reporter reporter) {
-		this.reporter = reporter;
-	}
-
-	public boolean canWrite() {
-		return false;
-	}
-
-	private File[] get(String bsn, String range) throws Exception {
-		VersionRange r = new VersionRange(range);
-		Map<String,String> instances = index.get(bsn);
-		if (instances == null)
-			return null;
-
-		List<File> result = Create.list();
-
-		for (Entry<String,String> entry : instances.entrySet()) {
-			if (r.includes(new Version(entry.getKey()))) {
-				File f = new File(entry.getValue());
-				if (f.isFile()) {
-					result.add(f);
-				}
-			}
-		}
-		return result.toArray(new File[result.size()]);
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public List<String> list(String regex) {
-		Instruction pattern = null;
-		if (regex != null)
-			pattern = new Instruction(regex);
-
-		List<String> result = new ArrayList<String>();
-		for (String f : index.keySet()) {
-			if (pattern == null || pattern.matches(f))
-				result.add(f);
-		}
-		return result;
-	}
-
-	public PutResult put(InputStream stream, PutOptions options) throws Exception {
-		return null;
-	}
-
-	public List<Version> versions(String bsn) {
-		Map<String,String> instances = index.get(bsn);
-		if (instances == null)
-			return null;
-
-		List<Version> versions = Create.list();
-		for (String v : instances.keySet())
-			versions.add(new Version(v));
-		return versions;
-	}
-
-	public File get(String bsn, String range, Strategy strategy, Map<String,String> properties) throws Exception {
-		File[] files = get(bsn, range);
-		if (files.length >= 0) {
-			switch (strategy) {
-				case LOWEST :
-					return files[0];
-				case HIGHEST :
-					return files[files.length - 1];
-				case EXACT :
-					// TODO exact version handing
-					break;
-			}
-		}
-		return null;
-	}
-
-	public String getLocation() {
-		return root.toString();
-	}
-}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Actionable.java b/bundleplugin/src/main/java/aQute/bnd/service/Actionable.java
new file mode 100644
index 0000000..4e805a5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Actionable.java
@@ -0,0 +1,48 @@
+package aQute.bnd.service;
+
+import java.util.*;
+
+/**
+ * An interface to allow bnd to provide commands on elements. This interface can
+ * provide information about the implementer but it can also provide information
+ * about its elements. These elements are identified by a <i>target</i>. A
+ * target is one or more objects that uniquely identify a child in the
+ * container. The exact protocol for the target is left to the implementers,
+ * this interface is just a conduit between the bnd world (no Eclipse etc) and
+ * the GUI world, using only bnd and java interfaces.
+ */
+public interface Actionable {
+	/**
+	 * Return a map with command names (potentially localized) and a Runnable.
+	 * The caller can execute the caller at will.
+	 * 
+	 * @param target
+	 *            the target object, null if commands for the encompassing
+	 *            entity is sought (e.g. the repo itself).
+	 * @return A Map with the actions or null if no actions are available.
+	 * @throws Exception
+	 */
+	Map<String,Runnable> actions(Object... target) throws Exception;
+
+	/**
+	 * Return a tooltip for the given target or the encompassing entity if null
+	 * is passed.
+	 * 
+	 * @param target
+	 *            the target, any number of parameters to identify
+	 * @return the tooltip or null
+	 * @throws Exception
+	 */
+	String tooltip(Object... target) throws Exception;
+
+	/**
+	 * Provide a title for an element.
+	 * 
+	 * @param target
+	 *            the target, any number of parameters to identify
+	 * @return the text for this element
+	 * @throws Exception
+	 */
+
+	String title(Object... target) throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Refreshable.java b/bundleplugin/src/main/java/aQute/bnd/service/Refreshable.java
index aeac258..73afa43 100644
--- a/bundleplugin/src/main/java/aQute/bnd/service/Refreshable.java
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Refreshable.java
@@ -3,7 +3,7 @@
 import java.io.*;
 
 public interface Refreshable {
-	boolean refresh();
+	boolean refresh() throws Exception;
 
 	File getRoot();
 }
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java
index 8110fab..e87f3b5 100644
--- a/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RemoteRepositoryPlugin.java
@@ -7,17 +7,16 @@
 	/**
 	 * Retrieve a resource handle from the repository. For all implementations
 	 * of this interface, calling {@code getFile(bsn, range, strategy, props)}
-	 * should always return the same result as
-	 * {@code getResource(bsn, range, strategy, props).request()}.
+	 * should always return the same result as {@link RepositoryPlugin#get(String, aQute.bnd.version.Version, Map)}
 	 * 
-	 * @param bsn
-	 * @param range
-	 * @param strategy
-	 * @param properties
+	 * @param bsn the bsn of the revision
+	 * @param version the version of the revision
+	 * @param strategy strategy
+	 * @param properties any properties
 	 * @return
 	 * @throws Exception
 	 */
-	ResourceHandle getHandle(String bsn, String range, Strategy strategy, Map<String,String> properties)
+	ResourceHandle getHandle(String bsn, String version, Strategy strategy, Map<String,String> properties)
 			throws Exception;
 
 	File getCacheDirectory();
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java
index 73847f7..f3ee1f0 100644
--- a/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java
+++ b/bundleplugin/src/main/java/aQute/bnd/service/RepositoryPlugin.java
@@ -6,113 +6,63 @@
 
 import aQute.bnd.version.*;
 
+/**
+ * A Repository Plugin abstract a bnd repository. This interface allows bnd to
+ * find programs from their bsn and revisions from their bsn-version
+ * combination. It is also possible to put revisions in a repository if the
+ * repository is not read only.
+ */
 public interface RepositoryPlugin {
-	public enum Strategy {
-		LOWEST, HIGHEST, EXACT
-	}
-
 	/**
 	 * Options used to steer the put operation
 	 */
-	public class PutOptions {
+	class PutOptions {
+		public String	BUNDLE	= "application/vnd.osgi.bundle";
+		public String	LIB		= "application/vnd.aQute.lib";
+
 		/**
-		 * The <b>SHA1</b> digest of the artifact to put into the repository.<br/>
-		 * <br/>
+		 * The <b>SHA1</b> digest of the artifact to put into the repository.
 		 * When specified the digest of the <b>fetched</b> artifact will be
 		 * calculated and verified against this digest, <b>before</b> putting
-		 * the artifact into the repository.<br/>
-		 * <br/>
-		 * An exception is thrown if the specified digest and the calculated
-		 * digest do not match.
+		 * the artifact into the repository. </p> An exception is thrown if the
+		 * specified digest and the calculated digest do not match.
 		 */
-		public byte[]	digest				= null;
+		public byte[]	digest	= null;
 
 		/**
-		 * Allow the implementation to change the artifact.<br/>
-		 * <br/>
-		 * When set to true the implementation is allowed to change the artifact
-		 * when putting it into the repository.<br/>
-		 * <br/>
-		 * An exception is thrown when set to false and the implementation can't
-		 * put the artifact into the repository without changing it.
+		 * Specify the mime type of the importing stream. This can be either
+		 * {@link #BUNDLE} or {@link #LIB}. If left open, it is up to the
+		 * repository to guess the content type.
 		 */
-		public boolean	allowArtifactChange	= false;
-
-		/**
-		 * Generate a <b>SHA1</b> digest.<br/>
-		 * <br/>
-		 * When set to true the implementation generates a digest of the
-		 * artifact as it is put into the repository and returns that digest in
-		 * the result.
-		 */
-		public boolean	generateDigest		= false;
-
-		/**
-		 * Create a 'latest' artifact when it did not exist.<br/>
-		 * <br/>
-		 * When set to true the implementation is requested to create a 'latest'
-		 * artifact.
-		 */
-		public boolean	createLatest		= false;
+		public String	type;
 	}
 
+	PutOptions	DEFAULTOPTIONS	= new PutOptions();
+
 	/**
 	 * Results returned by the put operation
 	 */
-	public class PutResult {
+	class PutResult {
 		/**
-		 * The artifact as it was put in the repository.<br/>
+		 * A (potentially public) uri to the revision as it was put in the
+		 * repository.<br/>
 		 * <br/>
-		 * This can be a URI to the artifact (when it was put into the
-		 * repository), or null when the artifact was not put into the
-		 * repository (for example because it was already in the repository).
+		 * This can be a URI to the given artifact (when it was put into the
+		 * repository). This does not have to be a File URI!
 		 */
 		public URI		artifact	= null;
 
 		/**
-		 * The 'latest' artifact as it was put in the repository.<br/>
-		 * <br/>
-		 * Only set when {@link PutOptions#createLatest} was set to true and the
-		 * 'latest' artifact did not exist, or when the 'latest' artifact did
-		 * exists and was older than the artifact being put in the repository.
-		 */
-		public URI		latest		= null;
-
-		/**
 		 * The <b>SHA1</b> digest of the artifact as it was put into the
 		 * repository.<br/>
 		 * <br/>
-		 * This will be null when {@link PutOptions#generateDigest} was null, or
-		 * when {@link #artifact} is null.
+		 * This can be null and it can differ from the input digest if the
+		 * repository rewrote the stream for optimization reason. If the
 		 */
 		public byte[]	digest		= null;
 	}
 
 	/**
-	 * Return a URL to a matching version of the given bundle.
-	 * 
-	 * @param bsn
-	 *            Bundle-SymbolicName of the searched bundle
-	 * @param range
-	 *            Version range for this bundle,"latest" if you only want the
-	 *            latest, or null when you want all.
-	 * @param strategy
-	 *            Get the highest or the lowest
-	 * @return A list of URLs sorted on version, lowest version is at index 0.
-	 *         null is returned when no files with the given bsn ould be found.
-	 * @throws Exception
-	 *             when anything goes wrong
-	 */
-	File get(String bsn, String range, Strategy strategy, Map<String,String> properties) throws Exception;
-
-	/**
-	 * Answer if this repository can be used to store files.
-	 * 
-	 * @return true if writable
-	 */
-	boolean canWrite();
-
-	/**
 	 * Put an artifact (from the InputStream) into the repository.<br/>
 	 * <br/>
 	 * There is NO guarantee that the artifact on the input stream has not been
@@ -123,7 +73,9 @@
 	 * @param stream
 	 *            The input stream with the artifact
 	 * @param options
-	 *            The put options. See {@link RepositoryPlugin.PutOptions}
+	 *            The put options. See {@link RepositoryPlugin.PutOptions}, can
+	 *            be {@code null}, which will then take the default options like
+	 *            new PutOptions().
 	 * @return The result of the put, never null. See
 	 *         {@link RepositoryPlugin.PutResult}
 	 * @throws Exception
@@ -138,16 +90,99 @@
 	PutResult put(InputStream stream, PutOptions options) throws Exception;
 
 	/**
+	 * The caller can specify any number of DownloadListener objects that are
+	 * called back when a download is finished (potentially before the get
+	 * method has returned).
+	 */
+
+	interface DownloadListener {
+		/**
+		 * Called when the file is successfully downloaded from a remote
+		 * repository.
+		 * 
+		 * @param file
+		 *            The file that was downloaded
+		 * @throws Exception
+		 *             , are logged and ignored
+		 */
+		void success(File file) throws Exception;
+
+		/**
+		 * Called when the file could not be downloaded from a remote
+		 * repository.
+		 * 
+		 * @param file
+		 *            The file that was intended to be downloaded.
+		 * @throws Exception
+		 *             , are logged and ignored
+		 */
+		void failure(File file, String reason) throws Exception;
+
+		/**
+		 * Can be called back regularly before success/failure but never after.
+		 * Indicates how far the download has progressed in percents. Since
+		 * downloads can be restarted, it is possible that the percentage
+		 * decreases.
+		 * 
+		 * @param file
+		 *            The file that was intended to be downloaded
+		 * @param percentage
+		 *            Percentage of file downloaded (can go down)
+		 * @return true if the download should continue, fails if it should be
+		 *         canceled (and fail)
+		 * @throws Exception
+		 *             , are logged and ignored
+		 */
+		boolean progress(File file, int percentage) throws Exception;
+	}
+
+	/**
+	 * Return a URL to a matching version of the given bundle.
+	 * <p/>
+	 * If download listeners are specified then the returned file is not
+	 * guaranteed to exist before a download listener is notified of success or
+	 * failure. The callback can happen before the method has returned. If the
+	 * returned file is null then download listeners are not called back.
+	 * <p/>
+	 * The intention of the Download Listeners is to allow a caller to obtain
+	 * references to files that do not yet exist but are to be downloaded. If
+	 * the downloads were done synchronously in the call, then no overlap of
+	 * downloads could take place.
+	 * 
+	 * @param bsn
+	 *            Bundle-SymbolicName of the searched bundle
+	 * @param version
+	 *            Version requested
+	 * @param listeners
+	 *            Zero or more download listener that will be notified of the
+	 *            outcome.
+	 * @return A file to the revision or null if not found
+	 * @throws Exception
+	 *             when anything goes wrong, in this case no listeners will be
+	 *             called back.
+	 */
+	File get(String bsn, Version version, Map<String,String> properties, DownloadListener... listeners)
+			throws Exception;
+
+	/**
+	 * Answer if this repository can be used to store files.
+	 * 
+	 * @return true if writable
+	 */
+	boolean canWrite();
+
+	/**
 	 * Return a list of bsns that are present in the repository.
 	 * 
-	 * @param regex
-	 *            if not null, match against the bsn and if matches, return
-	 *            otherwise skip
-	 * @return A list of bsns that match the regex parameter or all if regex is
-	 *         null
+	 * @param pattern
+	 *            A <ahref="https://en.wikipedia.org/wiki/Glob_%28programming%29">glob pattern</a>
+	 *            to be matched against bsns present in the repository, or {@code null}.
+	 * @return A list of bsns that match the pattern parameter or all if pattern
+	 *         is null; repositories that do not support browsing or querying
+	 *         should return an empty list.
 	 * @throws Exception
 	 */
-	List<String> list(String regex) throws Exception;
+	List<String> list(String pattern) throws Exception;
 
 	/**
 	 * Return a list of versions.
@@ -155,7 +190,7 @@
 	 * @throws Exception
 	 */
 
-	List<Version> versions(String bsn) throws Exception;
+	SortedSet<Version> versions(String bsn) throws Exception;
 
 	/**
 	 * @return The name of the repository
diff --git a/bundleplugin/src/main/java/aQute/bnd/service/Strategy.java b/bundleplugin/src/main/java/aQute/bnd/service/Strategy.java
new file mode 100644
index 0000000..c24cd37
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/service/Strategy.java
@@ -0,0 +1,5 @@
+package aQute.bnd.service;
+
+public enum Strategy {
+	LOWEST, EXACT, HIGHEST;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/settings/Settings.java b/bundleplugin/src/main/java/aQute/bnd/settings/Settings.java
deleted file mode 100644
index a52c613..0000000
--- a/bundleplugin/src/main/java/aQute/bnd/settings/Settings.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package aQute.bnd.settings;
-
-import java.security.*;
-import java.security.interfaces.*;
-import java.util.*;
-import java.util.prefs.*;
-
-import aQute.libg.cryptography.*;
-import aQute.libg.tuple.*;
-
-public class Settings {
-	public final static String	EMAIL			= "email";
-	public final static String	NAME			= "name";
-	public final static String	PASSWORD_SHA1	= "password.sha1";
-	final static String			KEY_PRIVATE		= "key.private";
-	final static String			KEY_PUBLIC		= "key.public";
-	final static String			KEY_SET			= "key.set";
-
-	static Preferences			prefs			= Preferences.userNodeForPackage(Settings.class);
-
-	public String globalGet(String key, String def) {
-		return prefs.get(key, def);
-	}
-
-	public void globalSet(String key, String value) throws BackingStoreException {
-		prefs.put(key, value);
-		prefs.sync();
-	}
-
-	public Collection<String> getKeys() throws BackingStoreException {
-		return Arrays.asList(prefs.keys());
-	}
-
-	public void globalRemove(String key) throws BackingStoreException {
-		prefs.remove(key);
-		prefs.sync();
-	}
-
-	private void generate() throws NoSuchAlgorithmException {
-		Pair< ? extends PrivateKey, ? extends RSAPublicKey> pair = RSA.generate();
-		prefs.put(KEY_PRIVATE, Crypto.toString(pair.getFirst()));
-		prefs.put(KEY_PUBLIC, Crypto.toString(pair.getSecond()));
-		prefs.putBoolean(KEY_SET, true);
-	}
-
-	public PrivateKey getPrivateKey() throws Exception {
-		if (prefs.getBoolean(KEY_SET, false))
-			generate();
-
-		String key = prefs.get(KEY_PRIVATE, null);
-		return Crypto.fromString(key, PrivateKey.class);
-	}
-
-	public PublicKey getPublicKey() throws Exception {
-		if (prefs.getBoolean(KEY_SET, false))
-			generate();
-
-		String key = prefs.get(KEY_PUBLIC, null);
-		return Crypto.fromString(key, PublicKey.class);
-	}
-
-}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/ExtList.java b/bundleplugin/src/main/java/aQute/lib/collections/ExtList.java
index 48e1732..0050a92 100644
--- a/bundleplugin/src/main/java/aQute/lib/collections/ExtList.java
+++ b/bundleplugin/src/main/java/aQute/lib/collections/ExtList.java
@@ -20,6 +20,11 @@
 		super(_);
 	}
 
+	public ExtList(Iterable<T> _) {
+		for ( T t : _)
+			add(t);
+	}
+
 	public static ExtList<String> from(String s) {
 		// TODO make sure no \ before comma
 		return from(s, "\\s*,\\s*");
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java b/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
index 4c475f1..189b97f 100644
--- a/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
+++ b/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
@@ -431,4 +431,8 @@
 		IteratorList<T> l = new IteratorList<T>(it);
 		return new SortedList<T>(l, cmp);
 	}
+
+	public static <T> SortedSet<T> empty() {
+		return (SortedSet<T>) empty;
+	}
 }
diff --git a/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java b/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java
deleted file mode 100644
index 6bb33b6..0000000
--- a/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package aQute.lib.deployer;
-
-import java.io.*;
-import java.util.*;
-import java.util.jar.*;
-import java.util.regex.*;
-
-import aQute.bnd.header.*;
-import aQute.bnd.osgi.*;
-import aQute.bnd.version.*;
-import aQute.lib.io.*;
-import aQute.service.reporter.*;
-
-public class FileInstallRepo extends FileRepo {
-
-	String		group;
-	boolean		dirty;
-	Reporter	reporter;
-	Pattern		REPO_FILE	= Pattern.compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+)\\.(jar|lib)");
-
-	@Override
-	public void setProperties(Map<String,String> map) {
-		super.setProperties(map);
-		group = map.get("group");
-	}
-
-	@Override
-	public void setReporter(Reporter reporter) {
-		super.setReporter(reporter);
-		this.reporter = reporter;
-	}
-
-	@Override
-	protected PutResult putArtifact(File tmpFile, PutOptions options) throws Exception {
-		assert (tmpFile != null);
-		assert (options != null);
-
-		Jar jar = null;
-		try {
-			init();
-			dirty = true;
-
-			jar = new Jar(tmpFile);
-
-			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);
-
-			if (reporter != null)
-				reporter.trace("bsn=%s version=%s", bsn, version);
-
-			File dir;
-			if (group == null) {
-				dir = getRoot();
-			} else {
-				dir = new File(getRoot(), group);
-				if (!dir.exists() && !dir.mkdirs()) {
-					throw new IOException("Could not create directory " + dir);
-				}
-			}
-			String fName = bsn + "-" + version.getWithoutQualifier() + ".jar";
-			File file = new File(dir, fName);
-
-			PutResult result = new PutResult();
-
-			if (reporter != null)
-				reporter.trace("updating %s ", file.getAbsolutePath());
-
-			if (file.exists()) {
-				IO.delete(file);
-			}
-			IO.rename(tmpFile, file);
-			result.artifact = file.toURI();
-
-			if (reporter != null)
-				reporter.progress(-1, "updated " + file.getAbsolutePath());
-
-			fireBundleAdded(jar, file);
-
-			File latest = new File(dir, bsn + "-latest.jar");
-			boolean latestExists = latest.exists() && latest.isFile();
-			boolean latestIsOlder = latestExists && (latest.lastModified() < jar.lastModified());
-			if ((options.createLatest && !latestExists) || latestIsOlder) {
-				if (latestExists) {
-					IO.delete(latest);
-				}
-				IO.copy(file, latest);
-				result.latest = latest.toURI();
-			}
-
-			return result;
-		}
-		finally {
-			if (jar != null) {
-				jar.close();
-			}
-		}
-	}
-
-	@Override
-	public boolean refresh() {
-		if (dirty) {
-			dirty = false;
-			return true;
-		}
-		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;
-	}
-}
diff --git a/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java b/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
index 2256f63..7fa7bb3 100644
--- a/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
+++ b/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
@@ -3,29 +3,204 @@
 import java.io.*;
 import java.security.*;
 import java.util.*;
-import java.util.jar.*;
 import java.util.regex.*;
 
-import aQute.bnd.header.*;
 import aQute.bnd.osgi.*;
+import aQute.bnd.osgi.Verifier;
 import aQute.bnd.service.*;
 import aQute.bnd.version.*;
+import aQute.lib.collections.*;
+import aQute.lib.hex.*;
 import aQute.lib.io.*;
+import aQute.libg.command.*;
+import aQute.libg.cryptography.*;
+import aQute.libg.reporter.*;
 import aQute.service.reporter.*;
 
-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";
+/**
+ * A FileRepo is the primary and example implementation of a repository based on
+ * a file system. It maintains its files in a bsn/bsn-version.jar style from a
+ * given location. It implements all the functions of the
+ * {@link RepositoryPlugin}, {@link Refreshable}, {@link Actionable}, and
+ * {@link Closeable}. The FileRepo can be extended or used as is. When used as
+ * is, it is possible to add shell commands to the life cycle of the FileRepo.
+ * This life cycle is as follows:
+ * <ul>
+ * <li>{@link #CMD_INIT} - Is only executed when the location did not exist</li>
+ * <li>{@link #CMD_OPEN} - Called (after init if necessary) to open it once</li>
+ * <li>{@link #CMD_REFRESH} - Called when refreshed.</li>
+ * <li>{@link #CMD_BEFORE_PUT} - Before the file system is changed</li>
+ * <li>{@link #CMD_AFTER_PUT} - After the file system has changed, and the put
+ * <li>{@link #CMD_BEFORE_GET} - Before the file is gotten</li>
+ * <li>{@link #CMD_AFTER_ACTION} - Before the file is gotten</li>
+ * <li>{@link #CMD_CLOSE} - When the repo is closed and no more actions will
+ * take place</li> was a success</li>
+ * <li>{@link #CMD_ABORT_PUT} - When the put is aborted.</li>
+ * <li>{@link #CMD_CLOSE} - To close the repository.</li>
+ * </ul>
+ * Additionally, it is possible to set the {@link #CMD_SHELL} and the
+ * {@link #CMD_PATH}. Notice that you can use the ${global} macro to read global
+ * (that is, machine local) settings from the ~/.bnd/settings.json file (can be
+ * managed with bnd).
+ */
+public class FileRepo implements Plugin, RepositoryPlugin, Refreshable, RegistryPlugin, Actionable, Closeable {
 
-	File[]						EMPTY_FILES	= new File[0];
+	/**
+	 * If set, will trace to stdout. Works only if no reporter is set.
+	 */
+	public final static String	TRACE				= "trace";
+
+	/**
+	 * Property name for the location of the repo, must be a valid path name
+	 * using forward slashes (see {@link IO#getFile(String)}.
+	 */
+	public final static String	LOCATION			= "location";
+
+	/**
+	 * Property name for the readonly state of the repository. If no, will
+	 * read/write, otherwise it must be a boolean value read by
+	 * {@link Boolean#parseBoolean(String)}. Read only repositories will not
+	 * accept writes.
+	 */
+	public final static String	READONLY			= "readonly";
+
+	/**
+	 * Set the name of this repository (optional)
+	 */
+	public final static String	NAME				= "name";
+
+	/**
+	 * Path property for commands. A comma separated path for directories to be
+	 * searched for command. May contain $ @} which will be replaced by the
+	 * system path. If this property is not set, the system path is assumed.
+	 */
+	public static final String	CMD_PATH			= "cmd.path";
+
+	/**
+	 * The name ( and path) of the shell to execute the commands. By default
+	 * this is sh and searched in the path.
+	 */
+	public static final String	CMD_SHELL			= "cmd.shell";
+
+	/**
+	 * Property for commands. The command only runs when the location does not
+	 * exist. </p>
+	 * 
+	 * @param rootFile
+	 *            the root of the repo (directory exists)
+	 */
+	public static final String	CMD_INIT			= "cmd.init";
+
+	/**
+	 * Property for commands. Command is run before the repo is first used. </p>
+	 * 
+	 * @param $0
+	 *            rootFile the root of the repo (directory exists)
+	 */
+	public static final String	CMD_OPEN			= "cmd.open";
+
+	/**
+	 * Property for commands. The command runs after a put operation. </p>
+	 * 
+	 * @param $0
+	 *            the root of the repo (directory exists)
+	 * @param $1
+	 *            the file that was put
+	 * @param $2
+	 *            the hex checksum of the file
+	 */
+	public static final String	CMD_AFTER_PUT		= "cmd.after.put";
+
+	/**
+	 * Property for commands. The command runs when the repository is refreshed.
+	 * </p>
+	 * 
+	 * @param $
+	 *            {0} the root of the repo (directory exists)
+	 */
+	public static final String	CMD_REFRESH			= "cmd.refresh";
+
+	/**
+	 * Property for commands. The command runs after the file is put. </p>
+	 * 
+	 * @param $0
+	 *            the root of the repo (directory exists)
+	 * @param $1
+	 *            the path to a temporary file
+	 */
+	public static final String	CMD_BEFORE_PUT		= "cmd.before.put";
+
+	/**
+	 * Property for commands. The command runs when a put is aborted after file
+	 * changes were made. </p>
+	 * 
+	 * @param $0
+	 *            the root of the repo (directory exists)
+	 * @param $1
+	 *            the temporary file that was used (optional)
+	 */
+	public static final String	CMD_ABORT_PUT		= "cmd.abort.put";
+
+	/**
+	 * Property for commands. The command runs after the file is put. </p>
+	 * 
+	 * @param $0
+	 *            the root of the repo (directory exists)
+	 */
+	public static final String	CMD_CLOSE			= "cmd.close";
+
+	/**
+	 * Property for commands. Will be run after an action has been executed.
+	 * </p>
+	 * 
+	 * @param $0
+	 *            the root of the repo (directory exists)
+	 * @param $1
+	 *            the path to the file that the action was executed on
+	 * @param $2
+	 *            the action executed
+	 */
+	public static final String	CMD_AFTER_ACTION	= "cmd.after.action";
+
+	/**
+	 * Called before a before get.
+	 * 
+	 * @param $0
+	 *            the root of the repo (directory exists)
+	 * @param $1
+	 *            the bsn
+	 * @param $2
+	 *            the version
+	 */
+	public static final String	CMD_BEFORE_GET		= "cmd.before.get";
+
+	/**
+	 * Options used when the options are null
+	 */
+	static final PutOptions		DEFAULTOPTIONS		= new PutOptions();
+
+	String						shell;
+	String						path;
+	String						init;
+	String						open;
+	String						refresh;
+	String						beforePut;
+	String						afterPut;
+	String						abortPut;
+	String						beforeGet;
+	String						close;
+	String						action;
+
+	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)");
+	boolean						canWrite			= true;
+	Pattern						REPO_FILE			= Pattern.compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+)\\.(jar|lib)");
 	Reporter					reporter;
 	boolean						dirty;
 	String						name;
+	boolean						inited;
+	boolean						trace;
 
 	public FileRepo() {}
 
@@ -35,280 +210,200 @@
 		this.canWrite = canWrite;
 	}
 
-	protected void init() throws Exception {
-		// for extensions
+	/**
+	 * Initialize the repository Subclasses should first call this method and
+	 * then if it returns true, do their own initialization
+	 * 
+	 * @return true if initialized, false if already had been initialized.
+	 * @throws Exception
+	 */
+	protected boolean init() throws Exception {
+		if (inited)
+			return false;
+
+		inited = true;
+
+		if (reporter == null) {
+			ReporterAdapter reporter = trace ? new ReporterAdapter(System.out) : new ReporterAdapter();
+			reporter.setTrace(trace);
+			reporter.setExceptions(trace);
+			this.reporter = reporter;
+		}
+
+		if (!root.isDirectory()) {
+			root.mkdirs();
+			if (!root.isDirectory())
+				throw new IllegalArgumentException("Location cannot be turned into a directory " + root);
+
+			exec(init, root.getAbsolutePath());
+		}
+		open();
+		return true;
 	}
 
+	/**
+	 * @see aQute.bnd.service.Plugin#setProperties(java.util.Map)
+	 */
 	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);
-
+		root = IO.getFile(IO.home, location);
 		String readonly = map.get(READONLY);
 		if (readonly != null && Boolean.valueOf(readonly).booleanValue())
 			canWrite = false;
 
 		name = map.get(NAME);
+		path = map.get(CMD_PATH);
+		shell = map.get(CMD_SHELL);
+		init = map.get(CMD_INIT);
+		open = map.get(CMD_OPEN);
+		refresh = map.get(CMD_REFRESH);
+		beforePut = map.get(CMD_BEFORE_PUT);
+		abortPut = map.get(CMD_ABORT_PUT);
+		afterPut = map.get(CMD_AFTER_PUT);
+		beforeGet = map.get(CMD_BEFORE_GET);
+		close = map.get(CMD_CLOSE);
+		action = map.get(CMD_AFTER_ACTION);
+
+		trace = map.get(TRACE) != null && Boolean.parseBoolean(map.get(TRACE));
 	}
 
 	/**
-	 * Get a list of URLs to bundles that are constrained by the bsn and
-	 * versionRange.
+	 * Answer if this repository can write.
 	 */
-	private 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;
 	}
 
-	protected PutResult putArtifact(File tmpFile, PutOptions options) throws Exception {
+	/**
+	 * Local helper method that tries to insert a file in the repository. This
+	 * method can be overridden but MUST not change the content of the tmpFile.
+	 * This method should also create a latest version of the artifact for
+	 * reference by tools like ant etc. </p> It is allowed to rename the file,
+	 * the tmp file must be beneath the root directory to prevent rename
+	 * problems.
+	 * 
+	 * @param tmpFile
+	 *            source file
+	 * @param digest
+	 * @return a File that contains the content of the tmpFile
+	 * @throws Exception
+	 */
+	protected File putArtifact(File tmpFile, byte[] digest) throws Exception {
 		assert (tmpFile != null);
-		assert (options != null);
 
-		Jar jar = null;
+		Jar jar = new Jar(tmpFile);
 		try {
-			init();
 			dirty = true;
 
-			jar = new Jar(tmpFile);
-
-			Manifest manifest = jar.getManifest();
-			if (manifest == null)
-				throw new IllegalArgumentException("No manifest in JAR: " + jar);
-
-			String bsn = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_SYMBOLICNAME);
+			String bsn = jar.getBsn();
 			if (bsn == null)
-				throw new IllegalArgumentException("No Bundle SymbolicName set");
+				throw new IllegalArgumentException("No bsn set in jar: " + tmpFile);
 
-			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;
+			String versionString = jar.getVersion();
 			if (versionString == null)
-				version = new Version();
-			else
-				version = new Version(versionString);
+				versionString = "0";
+			else if (!Verifier.isVersion(versionString))
+				throw new IllegalArgumentException("Incorrect version in : " + tmpFile + " " + versionString);
 
-			if (reporter != null)
-				reporter.trace("bsn=%s version=%s", bsn, version);
+			Version version = new Version(versionString);
+
+			reporter.trace("bsn=%s version=%s", bsn, version);
 
 			File dir = new File(root, bsn);
-			if (!dir.exists() && !dir.mkdirs()) {
+			dir.mkdirs();
+			if (!dir.isDirectory())
 				throw new IOException("Could not create directory " + dir);
-			}
+
 			String fName = bsn + "-" + version.getWithoutQualifier() + ".jar";
 			File file = new File(dir, fName);
 
-			boolean renamed = false;
-			PutResult result = new PutResult();
+			reporter.trace("updating %s ", file.getAbsolutePath());
 
-			if (reporter != null)
-				reporter.trace("updating %s ", file.getAbsolutePath());
-			if (!file.exists() || file.lastModified() < jar.lastModified()) {
-				if (file.exists()) {
-					IO.delete(file);
-				}
-				IO.rename(tmpFile, file);
-				renamed = true;
-				result.artifact = file.toURI();
+			IO.rename(tmpFile, file);
 
-				if (reporter != null)
-					reporter.progress(-1, "updated " + file.getAbsolutePath());
+			fireBundleAdded(jar, file);
+			afterPut(file, bsn, version, Hex.toHexString(digest));
 
-				fireBundleAdded(jar, file);
-			} else {
-				if (reporter != null) {
-					reporter.progress(-1, "Did not update " + jar + " because repo has a newer version");
-					reporter.trace("NOT Updating " + fName + " (repo is newer)");
-				}
-			}
-
+			// TODO like to beforeGet rid of the latest option. This is only
+			// used to have a constant name for the outside users (like ant)
+			// we should be able to handle this differently?
 			File latest = new File(dir, bsn + "-latest.jar");
-			boolean latestExists = latest.exists() && latest.isFile();
-			boolean latestIsOlder = latestExists && (latest.lastModified() < jar.lastModified());
-			if ((options.createLatest && !latestExists) || latestIsOlder) {
-				if (latestExists) {
-					IO.delete(latest);
-				}
-				if (!renamed) {
-					IO.rename(tmpFile, latest);
-				} else {
-					IO.copy(file, latest);
-				}
-				result.latest = latest.toURI();
-			}
+			IO.copy(file, latest);
 
-			return result;
+			reporter.trace("updated %s", file.getAbsolutePath());
+
+			return file;
 		}
 		finally {
-			if (jar != null) {
-				jar.close();
-			}
+			jar.close();
 		}
 	}
 
-	/* a straight copy of this method lives in LocalIndexedRepo */
+	/*
+	 * (non-Javadoc)
+	 * @see aQute.bnd.service.RepositoryPlugin#put(java.io.InputStream,
+	 * aQute.bnd.service.RepositoryPlugin.PutOptions)
+	 */
 	public PutResult put(InputStream stream, PutOptions options) throws Exception {
-		/* both parameters are required */
-		if ((stream == null) || (options == null)) {
-			throw new IllegalArgumentException("No stream and/or options specified");
-		}
-
 		/* determine if the put is allowed */
 		if (!canWrite) {
 			throw new IOException("Repository is read-only");
 		}
 
-		/* the root directory of the repository has to be a directory */
-		if (!root.isDirectory()) {
-			throw new IOException("Repository directory " + root + " is not a directory");
-		}
+		assert stream != null;
 
-		/* determine if the artifact needs to be verified */
-		boolean verifyFetch = (options.digest != null);
-		boolean verifyPut = !options.allowArtifactChange;
+		if (options == null)
+			options = DEFAULTOPTIONS;
 
-		/* determine which digests are needed */
-		boolean needFetchDigest = verifyFetch || verifyPut;
-		boolean needPutDigest = verifyPut || options.generateDigest;
+		init();
 
 		/*
-		 * setup a new stream that encapsulates the stream and calculates (when
-		 * needed) the digest
+		 * copy the artifact from the (new/digest) stream into a temporary file
+		 * in the root directory of the repository
 		 */
-		DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
-		dis.on(needFetchDigest);
-
-		File tmpFile = null;
+		File tmpFile = IO.createTempFile(root, "put", ".jar");
 		try {
-			/*
-			 * copy the artifact from the (new/digest) stream into a temporary
-			 * file in the root directory of the repository
-			 */
-			tmpFile = IO.createTempFile(root, "put", ".bnd");
-			IO.copy(dis, tmpFile);
+			DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
+			try {
+				IO.copy(dis, tmpFile);
 
-			/* get the digest if available */
-			byte[] disDigest = needFetchDigest ? dis.getMessageDigest().digest() : null;
+				byte[] digest = dis.getMessageDigest().digest();
 
-			/* verify the digest when requested */
-			if (verifyFetch && !MessageDigest.isEqual(options.digest, disDigest)) {
-				throw new IOException("Retrieved artifact digest doesn't match specified digest");
+				if (options.digest != null && !Arrays.equals(digest, options.digest))
+					throw new IOException("Retrieved artifact digest doesn't match specified digest");
+
+				/*
+				 * put the artifact into the repository (from the temporary
+				 * file)
+				 */
+				beforePut(tmpFile);
+				File file = putArtifact(tmpFile, digest);
+				file.setReadOnly();
+
+				PutResult result = new PutResult();
+				result.digest = digest;
+				result.artifact = file.toURI();
+
+				return result;
 			}
-
-			/* put the artifact into the repository (from the temporary file) */
-			PutResult r = putArtifact(tmpFile, options);
-
-			/* calculate the digest when requested */
-			if (needPutDigest && (r.artifact != null)) {
-				MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-				IO.copy(new File(r.artifact), sha1);
-				r.digest = sha1.digest();
+			finally {
+				dis.close();
 			}
-
-			/* verify the artifact when requested */
-			if (verifyPut && (r.digest != null) && !MessageDigest.isEqual(disDigest, r.digest)) {
-				File f = new File(r.artifact);
-				if (f.exists()) {
-					IO.delete(f);
-				}
-				throw new IOException("Stored artifact digest doesn't match specified digest");
-			}
-
-			return r;
+		}
+		catch (Exception e) {
+			abortPut(tmpFile);
+			throw e;
 		}
 		finally {
-			if (tmpFile != null && tmpFile.exists()) {
-				IO.delete(tmpFile);
-			}
-		}
-	}
-
-	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);
-			}
+			IO.delete(tmpFile);
 		}
 	}
 
 	public void setLocation(String string) {
-		root = new File(string);
-		if (!root.isDirectory())
-			throw new IllegalArgumentException("Invalid repository directory");
+		root = IO.getFile(string);
 	}
 
 	public void setReporter(Reporter reporter) {
@@ -344,7 +439,7 @@
 		return result;
 	}
 
-	public List<Version> versions(String bsn) throws Exception {
+	public SortedSet<Version> versions(String bsn) throws Exception {
 		init();
 		File dir = new File(root, bsn);
 		if (dir.isDirectory()) {
@@ -359,9 +454,9 @@
 					list.add(new Version(version));
 				}
 			}
-			return list;
+			return new SortedList<Version>(list);
 		}
-		return null;
+		return SortedList.empty();
 	}
 
 	@Override
@@ -373,7 +468,9 @@
 		return root;
 	}
 
-	public boolean refresh() {
+	public boolean refresh() throws Exception {
+		init();
+		exec(refresh, root);
 		if (dirty) {
 			dirty = false;
 			return true;
@@ -388,50 +485,26 @@
 		return name;
 	}
 
-	public Jar get(String bsn, Version v) throws Exception {
+	/*
+	 * (non-Javadoc)
+	 * @see aQute.bnd.service.RepositoryPlugin#get(java.lang.String,
+	 * aQute.bnd.version.Version, java.util.Map)
+	 */
+	public File get(String bsn, Version version, Map<String,String> properties, DownloadListener... listeners)
+			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);
-		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;
-			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];
-				case EXACT :
-					// TODO
-					break;
+		beforeGet(bsn, version);
+		File file = getLocal(bsn, version, properties);
+		if (file.exists()) {
+			for (DownloadListener l : listeners) {
+				try {
+					l.success(file);
+				}
+				catch (Exception e) {
+					reporter.exception(e, "Download listener for %s", file);
+				}
 			}
+			return file;
 		}
 		return null;
 	}
@@ -444,4 +517,258 @@
 		return root.toString();
 	}
 
+	public Map<String,Runnable> actions(Object... target) throws Exception {
+		if (target == null || target.length == 0)
+			return null; // no default actions
+
+		try {
+			String bsn = (String) target[0];
+			Version version = (Version) target[1];
+
+			final File f = get(bsn, version, null);
+			if (f == null)
+				return null;
+
+			Map<String,Runnable> actions = new HashMap<String,Runnable>();
+			actions.put("Delete " + bsn + "-" + status(bsn, version), new Runnable() {
+				public void run() {
+					IO.delete(f);
+					if (f.getParentFile().list().length == 0)
+						IO.delete(f.getParentFile());
+					afterAction(f, "delete");
+				};
+			});
+			return actions;
+		}
+		catch (Exception e) {
+			return null;
+		}
+	}
+
+	protected void afterAction(File f, String key) {
+		exec(action, root, f, key);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see aQute.bnd.service.Actionable#tooltip(java.lang.Object[])
+	 */
+	@SuppressWarnings("unchecked")
+	public String tooltip(Object... target) throws Exception {
+		if (target == null || target.length == 0)
+			return String.format("%s\n%s", getName(), root);
+
+		try {
+			String bsn = (String) target[0];
+			Version version = (Version) target[1];
+			Map<String,String> map = null;
+			if (target.length > 2)
+				map = (Map<String,String>) target[2];
+
+			File f = getLocal(bsn, version, map);
+			String s = String.format("Path: %s\nSize: %s\nSHA1: %s", f.getAbsolutePath(), readable(f.length(), 0), SHA1
+					.digest(f).asHex());
+			if (f.getName().endsWith(".lib") && f.isFile()) {
+				s += "\n" + IO.collect(f);
+			}
+			return s;
+
+		}
+		catch (Exception e) {
+			return null;
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see aQute.bnd.service.Actionable#title(java.lang.Object[])
+	 */
+	public String title(Object... target) throws Exception {
+		if (target == null || target.length == 0)
+			return getName();
+
+		if (target.length == 1 && target[0] instanceof String)
+			return (String) target[0];
+
+		if (target.length == 2 && target[0] instanceof String && target[1] instanceof Version) {
+			return status((String) target[0], (Version) target[1]);
+		}
+
+		return null;
+	}
+
+	protected File getLocal(String bsn, Version version, Map<String,String> properties) {
+		File dir = new File(root, bsn);
+
+		File fjar = new File(dir, bsn + "-" + version.getWithoutQualifier() + ".jar");
+		if (fjar.isFile())
+			return fjar.getAbsoluteFile();
+
+		File flib = new File(dir, bsn + "-" + version.getWithoutQualifier() + ".lib");
+		if (flib.isFile())
+			return flib.getAbsoluteFile();
+
+		return fjar.getAbsoluteFile();
+	}
+
+	protected String status(String bsn, Version version) {
+		File file = getLocal(bsn, version, null);
+		StringBuilder sb = new StringBuilder(version.toString());
+		String del = " [";
+
+		if (file.getName().endsWith(".lib")) {
+			sb.append(del).append("L");
+			del = "";
+		}
+		if (!file.getName().endsWith(".jar")) {
+			sb.append(del).append("?");
+			del = "";
+		}
+		if (!file.isFile()) {
+			sb.append(del).append("X");
+			del = "";
+		}
+		if (file.length() == 0) {
+			sb.append(del).append("0");
+			del = "";
+		}
+		if (del.equals(""))
+			sb.append("]");
+		return sb.toString();
+	}
+
+	private static String[]	names	= {
+			"bytes", "Kb", "Mb", "Gb"
+									};
+
+	private Object readable(long length, int n) {
+		if (length < 0)
+			return "<invalid>";
+
+		if (length < 1024 || n >= names.length)
+			return length + names[n];
+
+		return readable(length / 1024, n + 1);
+	}
+
+	public void close() throws IOException {
+		if (inited)
+			exec(close, root.getAbsolutePath());
+	}
+
+	protected void open() {
+		exec(open, root.getAbsolutePath());
+	}
+
+	protected void beforePut(File tmp) {
+		exec(beforePut, root.getAbsolutePath(), tmp.getAbsolutePath());
+	}
+
+	protected void afterPut(File file, String bsn, Version version, String sha) {
+		exec(afterPut, root.getAbsolutePath(), file.getAbsolutePath(), sha);
+	}
+
+	protected void abortPut(File tmpFile) {
+		exec(abortPut, root.getAbsolutePath(), tmpFile.getAbsolutePath());
+	}
+
+	protected void beforeGet(String bsn, Version version) {
+		exec(beforeGet, root.getAbsolutePath(), bsn, version);
+	}
+
+	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);
+			}
+		}
+	}
+
+	/**
+	 * Execute a command. Used in different stages so that the repository can be
+	 * synced with external tools.
+	 * 
+	 * @param line
+	 * @param target
+	 */
+	void exec(String line, Object... args) {
+		if (line == null)
+			return;
+
+		try {
+			if (args != null)
+				for (int i = 0; i < args.length; i++) {
+					if (i == 0)
+						line = line.replaceAll("\\$\\{@\\}", args[0].toString());
+					line = line.replaceAll("\\$" + i, args[i].toString());
+				}
+
+			if (shell == null) {
+				shell = System.getProperty("os.name").toLowerCase().indexOf("win") > 0 ? "cmd.exe" : "sh";
+			}
+			Command cmd = new Command(shell);
+
+			if (path != null) {
+				cmd.inherit();
+				String oldpath = cmd.var("PATH");
+				path = path.replaceAll("\\s*,\\s*", File.pathSeparator);
+				path = path.replaceAll("\\$\\{@\\}", oldpath);
+				cmd.var("PATH", path);
+			}
+
+			cmd.setCwd(getRoot());
+			StringBuilder stdout = new StringBuilder();
+			StringBuilder stderr = new StringBuilder();
+			int result = cmd.execute(line, stdout, stderr);
+			if (result != 0) {
+				reporter.error("Command %s failed with %s %s %s", line, result, stdout, stderr);
+			}
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+			reporter.exception(e, e.getMessage());
+		}
+	}
+
+	/*
+	 * 8 Set the root directory directly
+	 */
+	public void setDir(File repoDir) {
+		this.root = repoDir;
+	}
+
+	/**
+	 * Delete an entry from the repository and cleanup the directory
+	 * 
+	 * @param bsn
+	 * @param version
+	 * @throws Exception
+	 */
+	public void delete(String bsn, Version version) throws Exception {
+		assert bsn != null;
+
+		SortedSet<Version> versions;
+		if (version == null)
+			versions = versions(bsn);
+		else
+			versions = new SortedList<Version>(version);
+
+		for (Version v : versions) {
+			File f = getLocal(bsn, version, null);
+			if (!f.isFile())
+				reporter.error("No artifact found for %s:%s", bsn, version);
+			else
+				IO.delete(f);
+		}
+		if ( versions(bsn).isEmpty())
+			IO.delete( new File(root,bsn));
+	}
+
 }
diff --git a/bundleplugin/src/main/java/aQute/lib/io/IO.java b/bundleplugin/src/main/java/aQute/lib/io/IO.java
index f8a99da..0cb7e1b 100644
--- a/bundleplugin/src/main/java/aQute/lib/io/IO.java
+++ b/bundleplugin/src/main/java/aQute/lib/io/IO.java
@@ -7,6 +7,8 @@
 import java.util.*;
 
 public class IO {
+	static public File	work	= new File(System.getProperty("user.dir"));
+	static public File	home	= new File(System.getProperty("user.home"));
 
 	public static void copy(Reader r, Writer w) throws IOException {
 		try {
@@ -226,7 +228,7 @@
 
 	/**
 	 * Create a temporary file.
-	 *
+	 * 
 	 * @param directory
 	 *            the directory in which to create the file. Can be null, in
 	 *            which case the system TMP directory is used
@@ -257,16 +259,32 @@
 	}
 
 	public static File getFile(String filename) {
-		return new File(filename.replace("/", File.separator));
+		return getFile(work, filename);
 	}
-	
+
 	public static File getFile(File base, String file) {
+
+		if (file.startsWith("~/")) {
+			file = file.substring(2);
+			if (!file.startsWith("~/")) {
+				return getFile(home, file);
+			}
+		}
+		if (file.startsWith("~")) {
+			file = file.substring(1);
+			return getFile(home.getParentFile(), file);
+		}
+
 		File f = new File(file);
 		if (f.isAbsolute())
 			return f;
 		int n;
 
+		if (base == null)
+			base = work;
+
 		f = base.getAbsoluteFile();
+
 		while ((n = file.indexOf('/')) > 0) {
 			String first = file.substring(0, n);
 			file = file.substring(n + 1);
@@ -280,28 +298,35 @@
 		return new File(f, file).getAbsoluteFile();
 	}
 
-	/** Deletes the specified file.
-	 * Folders are recursively deleted.<br>
+	/**
+	 * Deletes the specified file. Folders are recursively deleted.<br>
 	 * If file(s) cannot be deleted, no feedback is provided (fail silently).
-	 * @param f file to be deleted
+	 * 
+	 * @param f
+	 *            file to be deleted
 	 */
 	public static void delete(File f) {
 		try {
 			deleteWithException(f);
-		} catch (IOException e) {
+		}
+		catch (IOException e) {
 			// Ignore a failed delete
 		}
 	}
-	
-	/** Deletes the specified file.
-	 * Folders are recursively deleted.<br>
+
+	/**
+	 * Deletes the specified file. Folders are recursively deleted.<br>
 	 * Throws exception if any of the files could not be deleted.
-	 * @param f file to be deleted
-	 * @throws IOException if the file (or contents of a folder) could not be deleted
+	 * 
+	 * @param f
+	 *            file to be deleted
+	 * @throws IOException
+	 *             if the file (or contents of a folder) could not be deleted
 	 */
 	public static void deleteWithException(File f) throws IOException {
 		f = f.getAbsoluteFile();
-		if (!f.exists()) return;
+		if (!f.exists())
+			return;
 		if (f.getParentFile() == null)
 			throw new IllegalArgumentException("Cannot recursively delete root for safety reasons");
 
@@ -311,7 +336,8 @@
 			for (File sub : subs) {
 				try {
 					deleteWithException(sub);
-				} catch (IOException e) {
+				}
+				catch (IOException e) {
 					wasDeleted = false;
 				}
 			}
@@ -323,19 +349,25 @@
 		}
 	}
 
-    /** Deletes <code>to</code> file if it exists, and renames <code>from</code> file to <code>to</code>.<br>
-     * Throws exception the rename operation fails.
-     * @param from source file
-     * @param to destination file
-     * @throws IOException if the rename operation fails
-     */
-    public static void rename(File from, File to) throws IOException {
-    	IO.deleteWithException(to);
-    	
-    	boolean renamed = from.renameTo(to);
-    	if (!renamed) throw new IOException("Could not rename " + from.getAbsoluteFile() + " to " + to.getAbsoluteFile());
-    }
+	/**
+	 * Deletes <code>to</code> file if it exists, and renames <code>from</code>
+	 * file to <code>to</code>.<br>
+	 * Throws exception the rename operation fails.
+	 * 
+	 * @param from
+	 *            source file
+	 * @param to
+	 *            destination file
+	 * @throws IOException
+	 *             if the rename operation fails
+	 */
+	public static void rename(File from, File to) throws IOException {
+		IO.deleteWithException(to);
 
+		boolean renamed = from.renameTo(to);
+		if (!renamed)
+			throw new IOException("Could not rename " + from.getAbsoluteFile() + " to " + to.getAbsoluteFile());
+	}
 
 	public static long drain(InputStream in) throws IOException {
 		long result = 0;
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
index a336e97..7622ae7 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
@@ -4,17 +4,17 @@
 import java.lang.reflect.*;
 import java.util.*;
 
-import aQute.lib.base64.*;
 import aQute.lib.hex.*;
 
+/**
+ * 
+ * Will now use hex for encoding byte arrays
+ *
+ */
 public class ByteArrayHandler extends Handler {
-
 	@Override
 	void encode(Encoder app, Object object, Map<Object,Type> visited) throws IOException, Exception {
-		if ( app.codec.isHex())
-			StringHandler.string(app, Hex.toHexString((byte[]) object));
-		else
-			StringHandler.string(app, Base64.encodeBase64((byte[]) object));
+		StringHandler.string(app, Hex.toHexString((byte[]) object));
 	}
 
 	@Override
@@ -31,8 +31,6 @@
 
 	@Override
 	Object decode(Decoder dec, String s) throws Exception {
-		if ( dec.codec.isHex())
-			return Hex.toByteArray(s);
-		return Base64.decodeBase64(s);
+		return Hex.toByteArray(s);
 	}
 }
diff --git a/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
index 47ad4ab..02601d4 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
@@ -34,6 +34,8 @@
  * <p/>
  * This Codec class can be used in a concurrent environment. The Decoders and
  * Encoders, however, must only be used in a single thread.
+ * <p/>
+ * Will now use hex for encoding byte arrays
  */
 public class JSONCodec {
 	final static String								START_CHARACTERS	= "[{\"-0123456789tfn";
@@ -51,7 +53,6 @@
 	private static ByteArrayHandler					byteh				= new ByteArrayHandler();
 
 	boolean											ignorenull;
-	boolean											useHex;
 
 	/**
 	 * Create a new Encoder with the state and appropriate API.
@@ -488,19 +489,4 @@
 		return ignorenull;
 	}
 
-	/**
-	 * Use hex instead of default base 64 encoding
-	 * 
-	 * @param useHex
-	 * @return
-	 */
-	public JSONCodec setHex(boolean useHex) {
-		this.useHex = useHex;
-		return this;
-	}
-
-	public boolean isHex() {
-		return useHex;
-	}
-
 }
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/lib/json/packageinfo b/bundleplugin/src/main/java/aQute/lib/json/packageinfo
index 86528b7..63eb236 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/packageinfo
+++ b/bundleplugin/src/main/java/aQute/lib/json/packageinfo
@@ -1 +1 @@
-version 2.4.0
+version 3.0.0
diff --git a/bundleplugin/src/main/java/aQute/lib/settings/Settings.java b/bundleplugin/src/main/java/aQute/lib/settings/Settings.java
new file mode 100644
index 0000000..55a149d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/settings/Settings.java
@@ -0,0 +1,251 @@
+package aQute.lib.settings;
+
+import java.io.*;
+import java.security.*;
+import java.security.spec.*;
+import java.util.*;
+
+import aQute.lib.io.*;
+import aQute.lib.json.*;
+
+/**
+ * Maintains persistent settings for bnd (or other apps). The default is
+ * ~/.bnd/settings.json). The settings are normal string properties but it
+ * specially maintains a public/private key pair and it provides a method to
+ * sign a byte array with this pair.
+ * <p/>
+ * Why not keystore and preferences? Well, keystore is hard to use (you can only
+ * store a private key when you have a certificate, but you cannot create a
+ * certificate without using com.sun classes) and preferences are not editable.
+ */
+public class Settings implements Map<String,String> {
+	static JSONCodec	codec	= new JSONCodec();
+
+	private File		where;
+	private PublicKey	publicKey;
+	private PrivateKey	privateKey;
+	private boolean		loaded;
+	private boolean		dirty;
+
+	public static class Data {
+		public int					version	= 1;
+		public byte[]				secret;
+		public byte[]				id;
+		public Map<String,String>	map		= new HashMap<String,String>();
+	}
+
+	Data	data	= new Data();
+
+	public Settings() {
+		this("~/.bnd/settings.json");
+	}
+
+	public Settings(String where) {
+		assert where != null;
+		this.where = IO.getFile(IO.work, where);
+	}
+
+	public boolean load() {
+		if (this.where.isFile() && this.where.length() > 1) {
+			try {
+				data = codec.dec().from(this.where).get(Data.class);
+				loaded = true;
+				return true;
+			}
+			catch (Exception e) {
+				throw new RuntimeException("Cannot read settings file " + this.where, e);
+			}
+		}
+
+		if (!data.map.containsKey("name"))
+			data.map.put("name", System.getProperty("user.name"));
+		return false;
+	}
+
+	private void check() {
+		if (loaded)
+			return;
+		load();
+		loaded = true;
+	}
+
+	public void save() {
+		if (!this.where.getParentFile().isDirectory() && !this.where.getParentFile().mkdirs())
+			throw new RuntimeException("Cannot create directory in " + this.where.getParent());
+
+		try {
+			codec.enc().to(this.where).put(data).flush();
+			assert this.where.isFile();
+		}
+		catch (Exception e) {
+			throw new RuntimeException("Cannot write settings file " + this.where, e);
+		}
+	}
+
+	public void generate() throws Exception {
+		KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+		SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+		keyGen.initialize(1024, random);
+		KeyPair pair = keyGen.generateKeyPair();
+		privateKey = pair.getPrivate();
+		publicKey = pair.getPublic();
+		data.secret = privateKey.getEncoded();
+		data.id = publicKey.getEncoded();
+		save();
+	}
+
+	public String getEmail() {
+		return get("email");
+	}
+
+	public void setEmail(String email) {
+		put("email", email);
+	}
+
+	public void setName(String v) {
+		put("name", v);
+	}
+
+	public String getName() {
+		String name = get("name");
+		if (name != null)
+			return name;
+		return System.getProperty("user.name");
+	}
+
+	/**
+	 * Return an encoded public RSA key. this key can be decoded with an
+	 * X509EncodedKeySpec
+	 * 
+	 * @return an encoded public key.
+	 * @throws Exception
+	 */
+	public byte[] getPublicKey() throws Exception {
+		initKeys();
+		return data.id;
+	}
+
+	/**
+	 * Return an encoded private RSA key. this key can be decoded with an
+	 * PKCS8EncodedKeySpec
+	 * 
+	 * @return an encoded private key.
+	 * @throws Exception
+	 */
+	public byte[] getPrivateKey() throws Exception {
+		initKeys();
+		return data.secret;
+	}
+
+	/*
+	 * Initialize the keys.
+	 */
+	private void initKeys() throws Exception {
+		check();
+		if (publicKey != null)
+			return;
+
+		if (data.id == null || data.secret == null) {
+			generate();
+		} else {
+			PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(data.secret);
+			X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(data.id);
+			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+			privateKey = keyFactory.generatePrivate(privateKeySpec);
+			publicKey = keyFactory.generatePublic(publicKeySpec);
+		}
+	}
+
+	/**
+	 * Sign a byte array
+	 */
+	public byte[] sign(byte[] con) throws Exception {
+		initKeys();
+
+		Signature hmac = Signature.getInstance("SHA1withRSA");
+		hmac.initSign(privateKey);
+		hmac.update(con);
+		return hmac.sign();
+	}
+
+	/**
+	 * Verify a signed byte array
+	 */
+	public boolean verify(byte[] con) throws Exception {
+		initKeys();
+
+		Signature hmac = Signature.getInstance("SHA1withRSA");
+		hmac.initVerify(publicKey);
+		hmac.update(con);
+		return hmac.verify(con);
+	}
+
+	public void clear() {
+		data = new Data();
+		IO.delete(where);
+	}
+
+	public boolean containsKey(Object key) {
+		check();
+		return data.map.containsKey(key);
+	}
+
+	public boolean containsValue(Object value) {
+		check();
+		return data.map.containsValue(value);
+	}
+
+	public Set<java.util.Map.Entry<String,String>> entrySet() {
+		check();
+		return data.map.entrySet();
+	}
+
+	public String get(Object key) {
+		check();
+
+		return data.map.get(key);
+	}
+
+	public boolean isEmpty() {
+		check();
+		return data.map.isEmpty();
+	}
+
+	public Set<String> keySet() {
+		check();
+		return data.map.keySet();
+	}
+
+	public String put(String key, String value) {
+		check();
+		dirty = true;
+		return data.map.put(key, value);
+	}
+
+	public void putAll(Map< ? extends String, ? extends String> v) {
+		check();
+		dirty = true;
+		data.map.putAll(v);
+	}
+
+	public String remove(Object key) {
+		check();
+		dirty = true;
+		return data.map.remove(key);
+	}
+
+	public int size() {
+		check();
+		return data.map.size();
+	}
+
+	public Collection<String> values() {
+		check();
+		return data.map.values();
+	}
+
+	public boolean isDirty() {
+		return dirty;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/settings/packageinfo b/bundleplugin/src/main/java/aQute/lib/settings/packageinfo
new file mode 100644
index 0000000..7ae9673
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/settings/packageinfo
@@ -0,0 +1 @@
+version 1.1
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/command/Command.java b/bundleplugin/src/main/java/aQute/libg/command/Command.java
index 8dd1678..f6114d9 100644
--- a/bundleplugin/src/main/java/aQute/libg/command/Command.java
+++ b/bundleplugin/src/main/java/aQute/libg/command/Command.java
@@ -68,7 +68,7 @@
 
 		if (timeout != 0) {
 			timer = new TimerTask() {
-				@Override
+				//@Override TODO why did this not work? TimerTask implements Runnable
 				public void run() {
 					timedout = true;
 					process.destroy();
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java b/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java
index 2785c3d..2722ab1 100644
--- a/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java
@@ -48,4 +48,13 @@
 	public abstract T digest(byte[] bytes) throws Exception;
 
 	public abstract String getAlgorithm();
+
+	public T from(File f) throws Exception {
+		IO.copy(f, this);
+		return digest();
+	}
+	public T from(byte[] f) throws Exception {
+		IO.copy(f, this);
+		return digest();
+	}
 }
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java b/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java
index a1a5a0a..c6def1b 100644
--- a/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java
@@ -36,6 +36,13 @@
 	}
 
 	public static MD5 digest(byte [] data) throws Exception {
-		return getDigester().digest(data);
+		return getDigester().from(data);
+	}
+
+	public static MD5 digest(File f) throws NoSuchAlgorithmException, Exception {
+		return getDigester().from(f);
+	}
+	public static MD5 digest(InputStream f) throws NoSuchAlgorithmException, Exception {
+		return getDigester().from(f);
 	}
 }
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java
index c76f182..c2f1e8d 100644
--- a/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java
@@ -36,6 +36,13 @@
 	}
 
 	public static SHA1 digest(byte [] data) throws Exception {
-		return getDigester().digest(data);
+		return getDigester().from(data);
+	}
+
+	public static SHA1 digest(File f) throws NoSuchAlgorithmException, Exception {
+		return getDigester().from(f);
+	}
+	public static SHA1 digest(InputStream f) throws NoSuchAlgorithmException, Exception {
+		return getDigester().from(f);
 	}
 }
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java
index 743a431..42baf4e 100644
--- a/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java
+++ b/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java
@@ -35,7 +35,15 @@
 		return ALGORITHM;
 	}
 
+
 	public static SHA256 digest(byte [] data) throws Exception {
-		return getDigester().digest(data);
+		return getDigester().from(data);
+	}
+
+	public static SHA256 digest(File f) throws NoSuchAlgorithmException, Exception {
+		return getDigester().from(f);
+	}
+	public static SHA256 digest(InputStream f) throws NoSuchAlgorithmException, Exception {
+		return getDigester().from(f);
 	}
 }
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java b/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java
index c8dcb41..30e744b 100755
--- a/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java
+++ b/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java
@@ -82,7 +82,7 @@
 			c = string.charAt(index++);
 			if (c == quote)
 				break;
-			if (c == '\\' && index < string.length() && string.charAt(index + 1) == quote)
+			if (c == '\\' && index < string.length() && string.charAt(index) == quote)
 				c = string.charAt(index++);
 			sb.append(c);
 		}
diff --git a/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java b/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java
index d233631..8c3f68b 100644
--- a/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java
+++ b/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java
@@ -246,20 +246,9 @@
 		return getInfo(other,null);
 	}
 	public boolean getInfo(Report other, String prefix) {
-		boolean ok = true;
-		if ( prefix == null)
-			prefix = "";
-		else
-			prefix = prefix + ": ";
-		for ( String error : other.getErrors()) {
-			errors.add( prefix + error);
-			ok = false;
-		}
-		
-		for ( String warning : other.getWarnings()) {
-			warnings.add( prefix + warning);
-		}
-		return ok;
+		addErrors(prefix, other.getErrors());
+		addWarnings(prefix, other.getWarnings());
+		return other.isOk();
 	}
 
 	public Location getLocation(String msg) {
@@ -285,4 +274,31 @@
 	public <T> T getMessages(Class<T> c) {
 		return ReporterMessages.base(this, c);
 	}
+	
+	/**
+	 * Add a number of errors
+	 */
+	
+	public void addErrors( String prefix, Collection<String> errors) {
+		if ( prefix == null)
+			prefix = "";
+		else
+			prefix = prefix + ": ";
+		for ( String s: errors) {
+			this.errors.add( prefix + s);
+		}
+	}
+	/**
+	 * Add a number of warnings
+	 */
+	
+	public void addWarnings( String prefix, Collection<String> warnings) {
+		if ( prefix == null)
+			prefix = "";
+		else
+			prefix = prefix + ": ";
+		for ( String s: warnings) {
+			this.warnings.add( prefix  + s);
+		}
+	}
 }
diff --git a/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java b/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java
index 84a0620..1bd8e07 100644
--- a/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java
+++ b/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java
@@ -268,7 +268,7 @@
 			}
 			catch (InvocationTargetException e) {
 				if (e.getCause() instanceof IllegalArgumentException) {
-					reporter.error("%s, for cmd: %s, arguments; %s", e.getMessage(), method, Arrays.toString(args));
+					reporter.error("%s, for cmd: %s, arguments; %s", e.getCause().getMessage(), method, Arrays.toString(args));
 				} else {
 					reporter.warning("Exception in replace: " + e.getCause());
 					e.getCause().printStackTrace();