Latest bnd sync

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1370165 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/lib/base64/Base64.java b/bundleplugin/src/main/java/aQute/lib/base64/Base64.java
index 777ac91..33f0a8a 100755
--- a/bundleplugin/src/main/java/aQute/lib/base64/Base64.java
+++ b/bundleplugin/src/main/java/aQute/lib/base64/Base64.java
@@ -98,6 +98,7 @@
 		data = decodeBase64(s);
 	}
 
+	@Override
 	public String toString() {
 		return encodeBase64(data);
 	}
diff --git a/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java b/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
index 0747fa3..4c475f1 100644
--- a/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
+++ b/bundleplugin/src/main/java/aQute/lib/collections/SortedList.java
@@ -364,11 +364,13 @@
 		return new SortedList<T>(this, fromIndex, toIndex);
 	}
 
+	@Override
 	@Deprecated
 	public boolean equals(Object other) {
 		return super.equals(other);
 	}
 
+	@Override
 	@Deprecated
 	public int hashCode() {
 		return super.hashCode();
@@ -393,6 +395,7 @@
 		this.type = type;
 	}
 
+	@Override
 	public String toString() {
 		StringBuilder sb = new StringBuilder();
 		sb.append("[");
diff --git a/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java b/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java
index 75ffd7a..6bb33b6 100644
--- a/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java
+++ b/bundleplugin/src/main/java/aQute/lib/deployer/FileInstallRepo.java
@@ -8,6 +8,7 @@
 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 {
@@ -17,63 +18,107 @@
 	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;
 	}
 
-	public File put(Jar jar) throws Exception {
-		dirty = true;
-		Manifest manifest = jar.getManifest();
-		if (manifest == null)
-			throw new IllegalArgumentException("No manifest in JAR: " + jar);
+	@Override
+	protected PutResult putArtifact(File tmpFile, PutOptions options) throws Exception {
+		assert (tmpFile != null);
+		assert (options != null);
 
-		String bsn = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_SYMBOLICNAME);
-		if (bsn == null)
-			throw new IllegalArgumentException("No Bundle SymbolicName set");
+		Jar jar = null;
+		try {
+			init();
+			dirty = true;
 
-		Parameters b = Processor.parseHeader(bsn, null);
-		if (b.size() != 1)
-			throw new IllegalArgumentException("Multiple bsn's specified " + b);
+			jar = new Jar(tmpFile);
 
-		for (String key : b.keySet()) {
-			bsn = key;
-			if (!Verifier.SYMBOLICNAME.matcher(bsn).matches())
-				throw new IllegalArgumentException("Bundle SymbolicName has wrong format: " + bsn);
+			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;
 		}
-
-		String versionString = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_VERSION);
-		Version version;
-		if (versionString == null)
-			version = new Version();
-		else
-			version = new Version(versionString);
-
-		File dir;
-		if (group == null) {
-			dir = getRoot();
-		} else {
-			dir = new File(getRoot(), group);
-			dir.mkdirs();
+		finally {
+			if (jar != null) {
+				jar.close();
+			}
 		}
-		String fName = bsn + "-" + version.getMajor() + "." + version.getMinor() + "." + version.getMicro() + ".jar";
-		File file = new File(dir, fName);
-
-		jar.write(file);
-		fireBundleAdded(jar, file);
-
-		file = new File(dir, bsn + "-latest.jar");
-		if (file.isFile() && file.lastModified() < jar.lastModified()) {
-			jar.write(file);
-		}
-		return file;
 	}
 
+	@Override
 	public boolean refresh() {
 		if (dirty) {
 			dirty = false;
diff --git a/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java b/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
index 922b39c..2256f63 100644
--- a/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
+++ b/bundleplugin/src/main/java/aQute/lib/deployer/FileRepo.java
@@ -1,6 +1,7 @@
 package aQute.lib.deployer;
 
 import java.io.*;
+import java.security.*;
 import java.util.*;
 import java.util.jar.*;
 import java.util.regex.*;
@@ -118,64 +119,175 @@
 		return canWrite;
 	}
 
-	public File put(Jar jar) throws Exception {
-		init();
-		dirty = true;
+	protected PutResult putArtifact(File tmpFile, PutOptions options) throws Exception {
+		assert (tmpFile != null);
+		assert (options != null);
 
-		Manifest manifest = jar.getManifest();
-		if (manifest == null)
-			throw new IllegalArgumentException("No manifest in JAR: " + jar);
+		Jar jar = null;
+		try {
+			init();
+			dirty = true;
 
-		String bsn = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_SYMBOLICNAME);
-		if (bsn == null)
-			throw new IllegalArgumentException("No Bundle SymbolicName set");
+			jar = new Jar(tmpFile);
 
-		Parameters b = Processor.parseHeader(bsn, null);
-		if (b.size() != 1)
-			throw new IllegalArgumentException("Multiple bsn's specified " + b);
+			Manifest manifest = jar.getManifest();
+			if (manifest == null)
+				throw new IllegalArgumentException("No manifest in JAR: " + jar);
 
-		for (String key : b.keySet()) {
-			bsn = key;
-			if (!Verifier.SYMBOLICNAME.matcher(bsn).matches())
-				throw new IllegalArgumentException("Bundle SymbolicName has wrong format: " + bsn);
-		}
+			String bsn = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_SYMBOLICNAME);
+			if (bsn == null)
+				throw new IllegalArgumentException("No Bundle SymbolicName set");
 
-		String versionString = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_VERSION);
-		Version version;
-		if (versionString == null)
-			version = new Version();
-		else
-			version = new Version(versionString);
+			Parameters b = Processor.parseHeader(bsn, null);
+			if (b.size() != 1)
+				throw new IllegalArgumentException("Multiple bsn's specified " + b);
 
-		if (reporter != null)
-			reporter.trace("bsn=%s version=%s", bsn, version);
+			for (String key : b.keySet()) {
+				bsn = key;
+				if (!Verifier.SYMBOLICNAME.matcher(bsn).matches())
+					throw new IllegalArgumentException("Bundle SymbolicName has wrong format: " + bsn);
+			}
 
-		File dir = new File(root, bsn);
-		dir.mkdirs();
-		String fName = bsn + "-" + version.getWithoutQualifier() + ".jar";
-		File file = new File(dir, fName);
+			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("updating %s ", file.getAbsolutePath());
-		if (!file.exists() || file.lastModified() < jar.lastModified()) {
-			jar.write(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)");
+				reporter.trace("bsn=%s version=%s", bsn, version);
+
+			File dir = new File(root, bsn);
+			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);
+
+			boolean renamed = false;
+			PutResult result = new PutResult();
+
+			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();
+
+				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)");
+				}
+			}
+
+			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();
+			}
+
+			return result;
+		}
+		finally {
+			if (jar != null) {
+				jar.close();
 			}
 		}
+	}
 
-		File latest = new File(dir, bsn + "-latest.jar");
-		if (latest.exists() && latest.lastModified() < jar.lastModified()) {
-			jar.write(latest);
-			file = latest;
+	/* a straight copy of this method lives in LocalIndexedRepo */
+	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");
 		}
 
-		return file;
+		/* 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");
+		}
+
+		/* determine if the artifact needs to be verified */
+		boolean verifyFetch = (options.digest != null);
+		boolean verifyPut = !options.allowArtifactChange;
+
+		/* determine which digests are needed */
+		boolean needFetchDigest = verifyFetch || verifyPut;
+		boolean needPutDigest = verifyPut || options.generateDigest;
+
+		/*
+		 * setup a new stream that encapsulates the stream and calculates (when
+		 * needed) the digest
+		 */
+		DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
+		dis.on(needFetchDigest);
+
+		File tmpFile = null;
+		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");
+			}
+
+			/* 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();
+			}
+
+			/* 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;
+		}
+		finally {
+			if (tmpFile != null && tmpFile.exists()) {
+				IO.delete(tmpFile);
+			}
+		}
 	}
 
 	protected void fireBundleAdded(Jar jar, File file) {
@@ -252,6 +364,7 @@
 		return null;
 	}
 
+	@Override
 	public String toString() {
 		return String.format("%-40s r/w=%s", root.getAbsolutePath(), canWrite());
 	}
diff --git a/bundleplugin/src/main/java/aQute/lib/filter/Filter.java b/bundleplugin/src/main/java/aQute/lib/filter/Filter.java
index 853f617..2c4dd09 100755
--- a/bundleplugin/src/main/java/aQute/lib/filter/Filter.java
+++ b/bundleplugin/src/main/java/aQute/lib/filter/Filter.java
@@ -234,6 +234,7 @@
 			this.dict = dict;
 		}
 
+		@Override
 		Object getProp(String key) {
 			return dict.get(key);
 		}
@@ -265,14 +266,17 @@
 		return null;
 	}
 
+	@Override
 	public String toString() {
 		return filter;
 	}
 
+	@Override
 	public boolean equals(Object obj) {
 		return obj != null && obj instanceof Filter && filter.equals(((Filter) obj).filter);
 	}
 
+	@Override
 	public int hashCode() {
 		return filter.hashCode();
 	}
diff --git a/bundleplugin/src/main/java/aQute/lib/index/Index.java b/bundleplugin/src/main/java/aQute/lib/index/Index.java
index 02bb37c..c51286f 100644
--- a/bundleplugin/src/main/java/aQute/lib/index/Index.java
+++ b/bundleplugin/src/main/java/aQute/lib/index/Index.java
@@ -241,6 +241,7 @@
 			write();
 		}
 
+		@Override
 		public String toString() {
 			StringBuilder sb = new StringBuilder();
 			try {
@@ -334,6 +335,7 @@
 		return page;
 	}
 
+	@Override
 	public String toString() {
 		return root.toString();
 	}
diff --git a/bundleplugin/src/main/java/aQute/lib/io/IO.java b/bundleplugin/src/main/java/aQute/lib/io/IO.java
index 616dfa4..f8a99da 100644
--- a/bundleplugin/src/main/java/aQute/lib/io/IO.java
+++ b/bundleplugin/src/main/java/aQute/lib/io/IO.java
@@ -152,7 +152,9 @@
 				out.close();
 			}
 		} else if (a.isDirectory()) {
-			b.mkdirs();
+			if (!b.exists() && !b.mkdirs()) {
+				throw new IOException("Could not create directory " + b);
+			}
 			if (!b.isDirectory())
 				throw new IllegalArgumentException("target directory for a directory must be a directory: " + b);
 			File subs[] = a.listFiles();
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
index 957369f..a336e97 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
@@ -33,7 +33,6 @@
 	Object decode(Decoder dec, String s) throws Exception {
 		if ( dec.codec.isHex())
 			return Hex.toByteArray(s);
-		else
-			return Base64.decodeBase64(s);
+		return Base64.decodeBase64(s);
 	}
 }
diff --git a/bundleplugin/src/main/java/aQute/lib/json/Encoder.java b/bundleplugin/src/main/java/aQute/lib/json/Encoder.java
index 0808de3..034ea6a 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/Encoder.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/Encoder.java
@@ -92,6 +92,7 @@
 		return this;
 	}
 
+	@Override
 	public String toString() {
 		return app.toString();
 	}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java b/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java
index ba62a13..47a4eab 100644
--- a/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java
+++ b/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java
@@ -83,6 +83,7 @@
 	 * An object can be assigned to a string. This means that the stream is
 	 * interpreted as the object but stored in its complete in the string.
 	 */
+	@Override
 	Object decodeObject(Decoder r) throws Exception {
 		return collect(r, '}');
 	}
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java b/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
index 2f08e10..fc219c1 100644
--- a/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
+++ b/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
@@ -14,6 +14,7 @@
  */
 public class JPAComponent extends XMLTypeProcessor {
 
+	@Override
 	protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
 		List<XMLType> types = new ArrayList<XMLType>();
 		process(types, "jpa.xsl", "META-INF", "persistence.xml");
diff --git a/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java b/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
index ae524d3..5ec76b0 100644
--- a/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
+++ b/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
@@ -14,6 +14,7 @@
  */
 public class SpringXMLType extends XMLTypeProcessor {
 
+	@Override
 	protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
 		List<XMLType> types = new ArrayList<XMLType>();
 
diff --git a/bundleplugin/src/main/java/aQute/lib/tag/Tag.java b/bundleplugin/src/main/java/aQute/lib/tag/Tag.java
index cf8668e..cc305d8 100755
--- a/bundleplugin/src/main/java/aQute/lib/tag/Tag.java
+++ b/bundleplugin/src/main/java/aQute/lib/tag/Tag.java
@@ -154,6 +154,7 @@
 	 * Return a string representation of this Tag and all its children
 	 * recursively.
 	 */
+	@Override
 	public String toString() {
 		StringWriter sw = new StringWriter();
 		print(0, new PrintWriter(sw));