Bug 552594 - Fix framework extensions with built-in Java 9 app loader

In Java 9 the application class loader changed from a URLClassLoader to
an internal type jdk.internal.loader.ClassLoaders.AppClassLoader.  The
new AppClassLoader no longer has the addURL method that we used to add
framework extension content to the class loader when the framework is
loaded from the java class path.

This fix uses the method appendToClassPathForInstrumentation(String) on
the new AppClassLoader class. To do this extra work is needed to allow
reflection on the AppClassLoader class.  This is not ideal, but will
allow the framework extensions to work when the framework is launched
from the java application class path.

Note that Eclipse launcher uses its own class loader which still has
addURL method available.  This new approach is only attempted if the
class loader used to launch the framework does not have an available
addURL method.

Change-Id: I0a4195307db6fb87dbab08933584e1a798ccf34a
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java
index 5d9ced4..ec38d9b 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java
@@ -49,7 +49,7 @@
 	 * The setAccessible class will be defined in the java.base module which grants
 	 * it the ability to call setAccessible(true) on other types from the java.base module
 	 */
-	static final Collection<AccessibleObject> setAccessible;
+	public static final Collection<AccessibleObject> setAccessible;
 	static final Collection<ClassLoader> systemLoaders;
 	static {
 		Collection<AccessibleObject> result = null;
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/FrameworkExtensionInstaller.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/FrameworkExtensionInstaller.java
index 939e954..59f5701 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/FrameworkExtensionInstaller.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/FrameworkExtensionInstaller.java
@@ -14,6 +14,7 @@
 package org.eclipse.osgi.storage;
 
 import java.io.File;
+import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.MalformedURLException;
@@ -36,6 +37,7 @@
 import org.eclipse.osgi.internal.hookregistry.ActivatorHookFactory;
 import org.eclipse.osgi.internal.hookregistry.HookRegistry;
 import org.eclipse.osgi.internal.messages.Msg;
+import org.eclipse.osgi.internal.url.MultiplexingFactory;
 import org.eclipse.osgi.storage.BundleInfo.Generation;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.Bundle;
@@ -50,29 +52,40 @@
 public class FrameworkExtensionInstaller {
 	private static final ClassLoader CL = FrameworkExtensionInstaller.class.getClassLoader();
 	private static final Method ADD_FWK_URL_METHOD = findAddURLMethod(CL, "addURL"); //$NON-NLS-1$
+	private static final Method ADD_FWK_FILE_PATH_METHOD = ADD_FWK_URL_METHOD == null ? findAddFilePathMethod(CL, "appendToClassPathForInstrumentation") : null; //$NON-NLS-1$
 	private final ArrayMap<BundleActivator, Bundle> hookActivators = new ArrayMap<>(5);
 
 	private static Method findAddURLMethod(ClassLoader cl, String name) {
 		if (cl == null)
 			return null;
-		return findMethod(cl.getClass(), name, new Class[] {URL.class});
+		return findMethod(cl.getClass(), name, new Class[] {URL.class}, null);
+	}
+
+	private static Method findAddFilePathMethod(ClassLoader cl, String name) {
+		if (cl == null)
+			return null;
+		return findMethod(cl.getClass(), name, new Class[] {String.class}, MultiplexingFactory.setAccessible);
 	}
 
 	// recursively searches a class and it's superclasses for a (potentially inaccessable) method
-	private static Method findMethod(Class<?> clazz, String name, Class<?>[] args) {
+	private static Method findMethod(Class<?> clazz, String name, Class<?>[] args, Collection<AccessibleObject> setAccessible) {
 		if (clazz == null)
 			return null; // ends the recursion when getSuperClass returns null
 		try {
 			Method result = clazz.getDeclaredMethod(name, args);
-			result.setAccessible(true);
+			if (setAccessible != null) {
+				setAccessible.add(result);
+			} else {
+				result.setAccessible(true);
+			}
 			return result;
 		} catch (SecurityException e) {
 			// if we do not have the permissions then we will not find the method
 		} catch (NoSuchMethodException | RuntimeException e) {
 			// do nothing look in super class below
 			// have to avoid blowing up <clinit>
-		}  
-		return findMethod(clazz.getSuperclass(), name, args);
+		}
+		return findMethod(clazz.getSuperclass(), name, args, setAccessible);
 	}
 
 	private static void callAddURLMethod(URL arg) throws InvocationTargetException {
@@ -83,6 +96,14 @@
 		}
 	}
 
+	private static void callAddFilePathMethod(File file) throws InvocationTargetException {
+		try {
+			ADD_FWK_FILE_PATH_METHOD.invoke(CL, new Object[] {file.getCanonicalPath()});
+		} catch (Throwable t) {
+			throw new InvocationTargetException(t);
+		}
+	}
+
 	private final EquinoxConfiguration configuration;
 
 	public FrameworkExtensionInstaller(EquinoxConfiguration configuraiton) {
@@ -113,34 +134,36 @@
 			// framework extensions
 			return;
 		}
-		if (CL == null || ADD_FWK_URL_METHOD == null) {
-			// use the first revision as the blame
-			ModuleRevision revision = revisions.iterator().next();
-			throw new BundleException("Cannot support framework extension bundles without a public addURL(URL) method on the framework class loader: " + revision.getBundle()); //$NON-NLS-1$
-		}
 
 		for (ModuleRevision revision : revisions) {
-			File[] files = getExtensionFiles(revision);
-			if (files == null) {
-				return;
+			if (CL == null || (ADD_FWK_URL_METHOD == null && ADD_FWK_FILE_PATH_METHOD == null)) {
+				// use the first revision as the blame
+				throw new BundleException("Cannot support framework extension bundles without a public addURL(URL) or appendToClassPathForInstrumentation(String) method on the framework class loader: " + revision.getBundle()); //$NON-NLS-1$
 			}
+			File[] files = getExtensionFiles(revision);
 			for (File file : files) {
 				if (file == null) {
-					continue; 
+					continue;
 				}
 				try {
-					callAddURLMethod(StorageUtil.encodeFileURL(file));
-				}catch (InvocationTargetException | MalformedURLException e) {
+					if (ADD_FWK_URL_METHOD != null) {
+						callAddURLMethod(StorageUtil.encodeFileURL(file));
+					} else if (ADD_FWK_FILE_PATH_METHOD != null) {
+						callAddFilePathMethod(file);
+					}
+				} catch (InvocationTargetException | MalformedURLException e) {
 					throw new BundleException("Error adding extension content.", e); //$NON-NLS-1$
 				}
 			}
 		}
 
-		try {
-			// initialize the new urls
-			CL.loadClass("thisIsNotAClass"); //$NON-NLS-1$
-		} catch (ClassNotFoundException e) {
-			// do nothing
+		if (CL != null) {
+			try {
+				// initialize the new urls
+				CL.loadClass("thisIsNotAClass"); //$NON-NLS-1$
+			} catch (ClassNotFoundException e) {
+				// do nothing
+			}
 		}
 		if (systemModule != null) {
 			BundleContext systemContext = systemModule.getBundle().getBundleContext();