Bug 502209 - Reflection used for URL handler support breaks on Java 9

Change-Id: I8069523103f1e3966feb9856e2a1100eda6b02b2
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/EquinoxFactoryManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/EquinoxFactoryManager.java
index 90c98c0..c267a95 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/EquinoxFactoryManager.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/EquinoxFactoryManager.java
@@ -92,7 +92,7 @@
 		Object lock;
 		try {
 			Field streamHandlerLockField = URL.class.getDeclaredField("streamHandlerLock"); //$NON-NLS-1$
-			streamHandlerLockField.setAccessible(true);
+			MultiplexingFactory.setAccessible(streamHandlerLockField);
 			lock = streamHandlerLockField.get(null);
 		} catch (NoSuchFieldException noField) {
 			// could not find the lock, lets sync on the class object
@@ -229,7 +229,7 @@
 		for (int i = 0; i < fields.length; i++) {
 			boolean isStatic = Modifier.isStatic(fields[i].getModifiers());
 			if (instance != isStatic && fields[i].getType().equals(type)) {
-				fields[i].setAccessible(true);
+				MultiplexingFactory.setAccessible(fields[i]);
 				return fields[i];
 			}
 		}
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 3ec5d5a..094ac48 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
@@ -8,12 +8,13 @@
  *******************************************************************************/
 package org.eclipse.osgi.internal.url;
 
-import java.lang.reflect.Method;
-import java.util.LinkedList;
-import java.util.List;
+import java.lang.reflect.*;
+import java.net.URL;
+import java.util.*;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.internal.framework.EquinoxBundle;
 import org.eclipse.osgi.internal.framework.EquinoxContainer;
+import org.eclipse.osgi.storage.StorageUtil;
 import org.osgi.framework.*;
 
 /*
@@ -21,6 +22,46 @@
  * handle environments running multiple osgi frameworks with the same VM.
  */
 public abstract class MultiplexingFactory {
+	/**
+	 * As a short-term (hopefully) solution we use a special class which is defined
+	 * using the Unsafe class from the VM.  This class is an implementation of
+	 * Collection<AccessibleObject> simply to provide a method add(AccessibleObject)
+	 * which turns around and calls AccessibleObject.setAccessible(true).
+	 * <p>
+	 * The reason this is needed is to hack into the VM to get deep reflective access to
+	 * the java.net package for the various hacks we have to do to multiplex the
+	 * URL and Content handlers.  Note that on Java 9 deep reflection is not possible
+	 * by default on the java.net package.
+	 * <p>
+	 * 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;
+	static {
+		Collection<AccessibleObject> result = null;
+		try {
+			// Use reflection on Unsafe to avoid having to compile against it
+			Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); //$NON-NLS-1$
+			Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); //$NON-NLS-1$
+
+			// NOTE: deep reflection is allowed on sun.misc package for java 9.
+			theUnsafe.setAccessible(true);
+			Object unsafe = theUnsafe.get(null);
+
+			// using defineAnonymousClass here because it seems more simple to get what we need
+			Method defineAnonymousClass = unsafeClass.getMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class); //$NON-NLS-1$
+			// The SetAccessible bytes stored in a resource to avoid real loading of it (see SetAccessible.java.src for source).
+			String tResource = "SetAccessible.bytes"; //$NON-NLS-1$
+
+			byte[] bytes = StorageUtil.getBytes(MultiplexingFactory.class.getResource(tResource).openStream(), -1, 4000);
+			@SuppressWarnings("unchecked")
+			Class<Collection<AccessibleObject>> clazz = (Class<Collection<AccessibleObject>>) defineAnonymousClass.invoke(unsafe, URL.class, bytes, (Object[]) null);
+			result = clazz.getConstructor().newInstance();
+		} catch (Throwable t) {
+			// ingore as if there is no Unsafe
+		}
+		setAccessible = result;
+	}
 	protected EquinoxContainer container;
 	protected BundleContext context;
 	private List<Object> factories; // list of multiplexed factories
@@ -167,4 +208,12 @@
 		updated.remove(factory);
 		factories = updated.isEmpty() ? null : updated;
 	}
+
+	static void setAccessible(AccessibleObject o) {
+		if (setAccessible != null) {
+			setAccessible.add(o);
+		} else {
+			o.setAccessible(true);
+		}
+	}
 }
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingURLStreamHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingURLStreamHandler.java
index c82ab15..19f25b7 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingURLStreamHandler.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingURLStreamHandler.java
@@ -38,37 +38,37 @@
 			return;
 		try {
 			openConnectionMethod = URLStreamHandler.class.getDeclaredMethod("openConnection", new Class[] {URL.class}); //$NON-NLS-1$
-			openConnectionMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(openConnectionMethod);
 
 			openConnectionProxyMethod = URLStreamHandler.class.getDeclaredMethod("openConnection", new Class[] {URL.class, Proxy.class}); //$NON-NLS-1$
-			openConnectionProxyMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(openConnectionProxyMethod);
 
 			equalsMethod = URLStreamHandler.class.getDeclaredMethod("equals", new Class[] {URL.class, URL.class}); //$NON-NLS-1$
-			equalsMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(equalsMethod);
 
 			getDefaultPortMethod = URLStreamHandler.class.getDeclaredMethod("getDefaultPort", (Class[]) null); //$NON-NLS-1$
-			getDefaultPortMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(getDefaultPortMethod);
 
 			getHostAddressMethod = URLStreamHandler.class.getDeclaredMethod("getHostAddress", new Class[] {URL.class}); //$NON-NLS-1$
-			getHostAddressMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(getHostAddressMethod);
 
 			hashCodeMethod = URLStreamHandler.class.getDeclaredMethod("hashCode", new Class[] {URL.class}); //$NON-NLS-1$
-			hashCodeMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(hashCodeMethod);
 
 			hostsEqualMethod = URLStreamHandler.class.getDeclaredMethod("hostsEqual", new Class[] {URL.class, URL.class}); //$NON-NLS-1$
-			hostsEqualMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(hostsEqualMethod);
 
 			parseURLMethod = URLStreamHandler.class.getDeclaredMethod("parseURL", new Class[] {URL.class, String.class, Integer.TYPE, Integer.TYPE}); //$NON-NLS-1$
-			parseURLMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(parseURLMethod);
 
 			sameFileMethod = URLStreamHandler.class.getDeclaredMethod("sameFile", new Class[] {URL.class, URL.class}); //$NON-NLS-1$
-			sameFileMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(sameFileMethod);
 
 			setURLMethod = URLStreamHandler.class.getDeclaredMethod("setURL", new Class[] {URL.class, String.class, String.class, Integer.TYPE, String.class, String.class, String.class, String.class, String.class}); //$NON-NLS-1$
-			setURLMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(setURLMethod);
 
 			toExternalFormMethod = URLStreamHandler.class.getDeclaredMethod("toExternalForm", new Class[] {URL.class}); //$NON-NLS-1$
-			toExternalFormMethod.setAccessible(true);
+			MultiplexingFactory.setAccessible(toExternalFormMethod);
 
 			try {
 				handlerField = URL.class.getDeclaredField("handler"); //$NON-NLS-1$
@@ -77,7 +77,7 @@
 				if (handlerField == null)
 					throw e;
 			}
-			handlerField.setAccessible(true);
+			MultiplexingFactory.setAccessible(handlerField);
 		} catch (Exception e) {
 			factory.container.getLogServices().log(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, "initializeMethods", e); //$NON-NLS-1$
 			throw new RuntimeException(e.getMessage(), e);
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/SetAccessible.bytes b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/SetAccessible.bytes
new file mode 100644
index 0000000..479e88d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/SetAccessible.bytes
Binary files differ
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/SetAccessible.java.src b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/SetAccessible.java.src
new file mode 100644
index 0000000..0dbd624
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/SetAccessible.java.src
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2016 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ *******************************************************************************/
+package java.net;
+
+import java.lang.reflect.AccessibleObject;
+import java.util.Collection;
+import java.util.Iterator;
+
+public class SetAccessible implements Collection<AccessibleObject> {
+	// playing tricks to get around reflecting in java.net package on java 9
+	@Override
+	public boolean add(AccessibleObject e) {
+		e.setAccessible(true);
+		return true;
+	}
+
+	@Override
+	public int size() {
+		return 0;
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return false;
+	}
+
+	@Override
+	public boolean contains(Object o) {
+		return false;
+	}
+
+	@Override
+	public Iterator<AccessibleObject> iterator() {
+		return null;
+	}
+
+	@Override
+	public Object[] toArray() {
+		return null;
+	}
+
+	@Override
+	public <T> T[] toArray(T[] a) {
+		return null;
+	}
+
+	@Override
+	public boolean remove(Object o) {
+		return false;
+	}
+
+	@Override
+	public boolean containsAll(Collection<?> c) {
+		return false;
+	}
+
+	@Override
+	public boolean addAll(Collection<? extends AccessibleObject> c) {
+		return false;
+	}
+
+	@Override
+	public boolean removeAll(Collection<?> c) {
+		return false;
+	}
+
+	@Override
+	public boolean retainAll(Collection<?> c) {
+		return false;
+	}
+
+	@Override
+	public void clear() {
+		// nothing
+	}
+
+}