You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by mc...@apache.org on 2012/09/06 20:28:09 UTC

svn commit: r1381708 [3/3] - in /felix/trunk/bundleplugin/src/main/java/aQute: bnd/build/ bnd/build/model/ bnd/build/model/conversions/ bnd/component/ bnd/differ/ bnd/filerepo/ bnd/help/ bnd/make/calltree/ bnd/make/component/ bnd/make/coverage/ bnd/mav...

Modified: felix/trunk/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java Thu Sep  6 18:28:06 2012
@@ -3,29 +3,204 @@ package aQute.lib.deployer;
 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 @@ public class FileRepo implements Plugin,
 		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");
-
-			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);
-			}
+				throw new IllegalArgumentException("No bsn set in jar: " + tmpFile);
 
-			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);
-			} 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)");
-				}
-			}
+			fireBundleAdded(jar, file);
+			afterPut(file, bsn, version, Hex.toHexString(digest));
 
+			// 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);
-
-			/* get the digest if available */
-			byte[] disDigest = needFetchDigest ? dis.getMessageDigest().digest() : null;
-
-			/* verify the digest when requested */
-			if (verifyFetch && !MessageDigest.isEqual(options.digest, disDigest)) {
-				throw new IOException("Retrieved artifact digest doesn't match specified digest");
-			}
+			DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
+			try {
+				IO.copy(dis, tmpFile);
 
-			/* put the artifact into the repository (from the temporary file) */
-			PutResult r = putArtifact(tmpFile, options);
+				byte[] digest = dis.getMessageDigest().digest();
 
-			/* 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();
-			}
+				if (options.digest != null && !Arrays.equals(digest, options.digest))
+					throw new IOException("Retrieved artifact digest doesn't match specified digest");
 
-			/* 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");
-			}
+				/*
+				 * put the artifact into the repository (from the temporary
+				 * file)
+				 */
+				beforePut(tmpFile);
+				File file = putArtifact(tmpFile, digest);
+				file.setReadOnly();
 
-			return r;
-		}
-		finally {
-			if (tmpFile != null && tmpFile.exists()) {
-				IO.delete(tmpFile);
-			}
-		}
-	}
+				PutResult result = new PutResult();
+				result.digest = digest;
+				result.artifact = file.toURI();
 
-	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);
+				return result;
 			}
-			catch (Exception e) {
-				if (reporter != null)
-					reporter.warning("Repository listener threw an unexpected exception: %s", e);
+			finally {
+				dis.close();
 			}
 		}
+		catch (Exception e) {
+			abortPut(tmpFile);
+			throw e;
+		}
+		finally {
+			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 @@ public class FileRepo implements Plugin,
 		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 @@ public class FileRepo implements Plugin,
 					list.add(new Version(version));
 				}
 			}
-			return list;
+			return new SortedList<Version>(list);
 		}
-		return null;
+		return SortedList.empty();
 	}
 
 	@Override
@@ -373,7 +468,9 @@ public class FileRepo implements Plugin,
 		return root;
 	}
 
-	public boolean refresh() {
+	public boolean refresh() throws Exception {
+		init();
+		exec(refresh, root);
 		if (dirty) {
 			dirty = false;
 			return true;
@@ -388,60 +485,290 @@ public class FileRepo implements Plugin,
 		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);
+		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;
 	}
 
-	public File get(String bsn, String version, Strategy strategy, Map<String,String> properties) throws Exception {
-		if (version == null)
-			version = "0.0.0";
+	public void setRegistry(Registry registry) {
+		this.registry = registry;
+	}
 
-		if (strategy == Strategy.EXACT) {
-			VersionRange vr = new VersionRange(version);
-			if (vr.isRange())
-				return null;
+	public String getLocation() {
+		return root.toString();
+	}
 
-			if (vr.getHigh().getMajor() == Integer.MAX_VALUE)
-				version = "latest";
+	public Map<String,Runnable> actions(Object... target) throws Exception {
+		if (target == null || target.length == 0)
+			return null; // no default actions
 
-			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;
+		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;
 		}
-		File[] files = get(bsn, version);
-		if (files == null || files.length == 0)
+		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);
 
-		if (files.length >= 0) {
-			switch (strategy) {
-				case LOWEST :
-					return files[0];
-				case HIGHEST :
-					return files[files.length - 1];
-				case EXACT :
-					// TODO
-					break;
+		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;
 	}
 
-	public void setRegistry(Registry registry) {
-		this.registry = registry;
+	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();
 	}
 
-	public String getLocation() {
-		return root.toString();
+	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));
 	}
 
 }

Modified: felix/trunk/bundleplugin/src/main/java/aQute/lib/io/IO.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/io/IO.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/io/IO.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/io/IO.java Thu Sep  6 18:28:06 2012
@@ -7,6 +7,8 @@ import java.security.*;
 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 @@ public class IO {
 
 	/**
 	 * 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 class IO {
 	}
 
 	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 @@ public class IO {
 		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 @@ public class IO {
 			for (File sub : subs) {
 				try {
 					deleteWithException(sub);
-				} catch (IOException e) {
+				}
+				catch (IOException e) {
 					wasDeleted = false;
 				}
 			}
@@ -323,19 +349,25 @@ public class IO {
 		}
 	}
 
-    /** 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;

Modified: felix/trunk/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java Thu Sep  6 18:28:06 2012
@@ -4,17 +4,17 @@ import java.io.*;
 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 @@ public class ByteArrayHandler extends Ha
 
 	@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);
 	}
 }

Modified: felix/trunk/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java Thu Sep  6 18:28:06 2012
@@ -34,6 +34,8 @@ import java.util.regex.*;
  * <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 @@ public class JSONCodec {
 	private static ByteArrayHandler					byteh				= new ByteArrayHandler();
 
 	boolean											ignorenull;
-	boolean											useHex;
 
 	/**
 	 * Create a new Encoder with the state and appropriate API.
@@ -488,19 +489,4 @@ public class JSONCodec {
 		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

Modified: felix/trunk/bundleplugin/src/main/java/aQute/lib/json/packageinfo
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/json/packageinfo?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/json/packageinfo (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/json/packageinfo Thu Sep  6 18:28:06 2012
@@ -1 +1 @@
-version 2.4.0
+version 3.0.0

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/Settings.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/Settings.java?rev=1381708&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/Settings.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/Settings.java Thu Sep  6 18:28:06 2012
@@ -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;
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/Settings.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/Settings.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/packageinfo
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/packageinfo?rev=1381708&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/packageinfo (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/settings/packageinfo Thu Sep  6 18:28:06 2012
@@ -0,0 +1 @@
+version 1.1
\ No newline at end of file

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/command/Command.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/command/Command.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/command/Command.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/command/Command.java Thu Sep  6 18:28:06 2012
@@ -68,7 +68,7 @@ public class Command {
 
 		if (timeout != 0) {
 			timer = new TimerTask() {
-				@Override
+				//@Override TODO why did this not work? TimerTask implements Runnable
 				public void run() {
 					timedout = true;
 					process.destroy();

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/Digester.java Thu Sep  6 18:28:06 2012
@@ -48,4 +48,13 @@ public abstract class Digester<T extends
 	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();
+	}
 }

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/MD5.java Thu Sep  6 18:28:06 2012
@@ -36,6 +36,13 @@ public class MD5 extends Digest {
 	}
 
 	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

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA1.java Thu Sep  6 18:28:06 2012
@@ -36,6 +36,13 @@ public class SHA1 extends Digest {
 	}
 
 	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

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/cryptography/SHA256.java Thu Sep  6 18:28:06 2012
@@ -35,7 +35,15 @@ public class SHA256 extends Digest {
 		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

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/qtokens/QuotedTokenizer.java Thu Sep  6 18:28:06 2012
@@ -82,7 +82,7 @@ public class QuotedTokenizer {
 			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);
 		}

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/reporter/ReporterAdapter.java Thu Sep  6 18:28:06 2012
@@ -246,20 +246,9 @@ public class ReporterAdapter implements 
 		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 class ReporterAdapter implements 
 	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);
+		}
+	}
 }

Modified: felix/trunk/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java?rev=1381708&r1=1381707&r2=1381708&view=diff
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java (original)
+++ felix/trunk/bundleplugin/src/main/java/aQute/libg/sed/ReplacerAdapter.java Thu Sep  6 18:28:06 2012
@@ -268,7 +268,7 @@ public class ReplacerAdapter extends Rep
 			}
 			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();