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/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);
 	}