Bug 501617 - BuildPathSupport should support unexploded bundles

Support extracting requested library jars from a bundle to an internal
location.

Change-Id: I1c5aa0989d15aed4de219b2b20389165fa1529ea
Signed-off-by: Brian de Alwis <bsd@mt.ca>
diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/buildpath/BuildPathSupport.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/buildpath/BuildPathSupport.java
index f8f219b..36a51e1 100644
--- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/buildpath/BuildPathSupport.java
+++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/buildpath/BuildPathSupport.java
@@ -14,7 +14,12 @@
 import java.io.File;
 import java.io.FilenameFilter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.URL;
+import java.nio.file.Files;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
 
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Version;
@@ -25,8 +30,10 @@
 
 import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
 
 import org.eclipse.jdt.core.IAccessRule;
 import org.eclipse.jdt.core.IClasspathAttribute;
@@ -106,12 +113,97 @@
 			return null;
 		}
 		
+		private IPath getBundleFileLocation(String aBundleId, VersionRange aVersionRange, String filePath) {
+			BundleInfo bundleInfo = P2Utils.findBundle(aBundleId, aVersionRange, false);
+
+			if (bundleInfo != null) {
+				resolvedVersion = bundleInfo.getVersion();
+				IPath bundleLocation = P2Utils.getBundleLocationPath(bundleInfo);
+				if(bundleLocation != null) {
+					File bundleLoc = bundleLocation.toFile();
+					if(bundleLoc.isDirectory() && new File(bundleLoc, filePath).exists()) {
+						return bundleLocation.append(filePath);
+					} else if (bundleLoc.isFile() && bundleLoc.getName().endsWith(".jar")) { //$NON-NLS-1$
+						return extractArchiveEntry(bundleInfo, bundleLoc, filePath);
+					}
+				}
+			} else {
+				// p2's simple configurator is not available. Let's try with installed bundles from the running platform.
+				// Note: Source bundles are typically not available at run time!
+				Bundle[] bundles= Platform.getBundles(aBundleId, aVersionRange.toString());
+				Bundle bestMatch= null;
+				if (bundles != null) {
+					for (int i= 0; i < bundles.length; i++) {
+						Bundle bundle= bundles[i];
+						if (bestMatch == null || bundle.getState() > bestMatch.getState()) {
+							bestMatch= bundle;
+						}
+					}
+				}
+				if (bestMatch != null) {
+					try {
+						resolvedVersion= bestMatch.getVersion().toString();
+						URL rootUrl= bestMatch.getEntry(filePath);
+						if (rootUrl != null) {
+							URL fileRootUrl= FileLocator.toFileURL(rootUrl);
+							return new Path(fileRootUrl.getPath());
+						}
+					} catch (IOException ex) {
+						JUnitCorePlugin.log(ex);
+					}
+				}
+			}
+			return null;
+		}
+
+		/**
+		 * Extract the a library from a bundle to a known location.
+		 *
+		 * @param bundleInfo bundle information, used to create a reproducible location to hold the
+		 *            extracted library
+		 * @param bundleLoc the bundle jar
+		 * @param filePath the file path within the bundle of the library to be extracted
+		 * @return the path to the extracted library
+		 */
+		private IPath extractArchiveEntry(BundleInfo bundleInfo, File bundleLoc, String filePath) {
+			IPath container= JUnitCorePlugin.getDefault().getStateLocation().append(bundleInfo.getSymbolicName()).append(bundleInfo.getVersion());
+			IPath extractedPath= container.append(filePath);
+			if (extractedPath.toFile().exists()) {
+				// previously extracted
+				return extractedPath;
+			}
+			try (JarFile jar= new JarFile(bundleLoc)) {
+				ZipEntry entry= jar.getEntry(filePath);
+				if (entry != null) {
+					if (!container.toFile().exists() && !container.toFile().mkdirs()) {
+						JUnitCorePlugin.log(new Status(IStatus.ERROR, JUnitCorePlugin.CORE_PLUGIN_ID, "Unable to create directory to hold " + filePath)); //$NON-NLS-1$
+						return null;
+					}
+					try (InputStream input= jar.getInputStream(entry);
+							OutputStream output= Files.newOutputStream(extractedPath.toFile().toPath())) {
+						int bytesRead;
+						byte[] buffer= new byte[8192];
+						while ((bytesRead= input.read(buffer)) > 0) {
+							output.write(buffer, 0, bytesRead);
+						}
+					}
+					return extractedPath;
+				}
+			} catch (IOException ex) {
+				JUnitCorePlugin.log(ex);
+			}
+			return null;
+		}
+
 		public IClasspathEntry getLibraryEntry() {
 			IPath bundleLocation = getBundleLocation(bundleId, versionRange);
 			if (bundleLocation != null) {
 				IPath bundleRootLocation= null;
 				if (bundleRoot != null) {
-					bundleRootLocation= getLocationIfExists(bundleLocation, bundleRoot);
+					bundleRootLocation= getBundleFileLocation(bundleId, versionRange, bundleRoot);
+					if(bundleRootLocation == null) {
+						bundleRootLocation= getLocationIfExists(bundleLocation, bundleRoot);
+					}
 				}
 				if (bundleRootLocation == null && binaryImportedRoot != null) {
 					bundleRootLocation= getLocationIfExists(bundleLocation, binaryImportedRoot);