/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.lang.reflect.gs;
import gw.lang.Gosu;
import gw.lang.reflect.TypeSystem;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
/**
*/
public class GosuClassPathThing {
private static final String PROTOCOL_PACKAGE = "gw.internal.gosu.compiler.protocols";
private static void addGosuClassProtocolToClasspath() {
try {
URLClassLoaderWrapper urlLoader = findUrlLoader();
URL url = makeURL();
if (!urlLoader.getURLs().contains(url)) {
urlLoader.addURL(url);
}
}
catch( Exception e ) {
throw new RuntimeException( e );
}
}
private static URL makeURL() throws MalformedURLException {
String protocol = "gosuclass";
URL url;
try {
url = new URL( null, protocol + "://honeybadger/" );
}
catch( Exception e ) {
// If our Handler class is not in the system loader and not accessible within the Caller's
// classloader from the URL constructor (3 activation records deep), then our Handler class
// is not loadable by the URL class, but the honey badget doesn't really care; it gets
// what it wants.
addOurProtocolHandler();
url = new URL( null, protocol + "://honeybadger/" );
}
return url;
}
private static void addOurProtocolHandler() {
try {
Field field = URL.class.getDeclaredField( "handlers" );
field.setAccessible( true );
Method put = Hashtable.class.getMethod( "put", Object.class, Object.class );
Field instanceField = Class.forName( "gw.internal.gosu.compiler.protocols.gosuclass.Handler" ).getField( "INSTANCE" );
Object handler = instanceField.get( null );
put.invoke( field.get( null ), "gosuclass", handler );
} catch (Exception e) {
throw new IllegalStateException("Failed to configure gosu protocol handler", e);
}
}
private static void removeOurProtocolHandler() {
try {
Field field = URL.class.getDeclaredField( "handlers" );
field.setAccessible( true );
Method remove = Hashtable.class.getMethod( "remove", Object.class );
remove.invoke( field.get( null ), "gosuclass" );
} catch (Exception e) {
throw new IllegalStateException("Failed to cleanup gosu protocol handler", e);
}
}
private static boolean addOurProtocolPackage() {
// XXX: Do not add protocol package since OSGi implementation of URLStreamFactory
// first delegates to those and only then calls service from Service Registry
String strProtocolProp = "java.protocol.handler.pkgs";
String protocols = PROTOCOL_PACKAGE;
String oldProp = System.getProperty( strProtocolProp );
if( oldProp != null ) {
if( oldProp.contains( PROTOCOL_PACKAGE ) ) {
return false;
}
protocols += '|' + oldProp;
}
System.setProperty( strProtocolProp, protocols );
return true;
}
private static void removeOurProtocolPackage() {
String strProtocolProp = "java.protocol.handler.pkgs";
String ours = "gw.internal.gosu.compiler.protocols";
String protocols = System.getProperty( strProtocolProp );
if( protocols != null ) {
// Remove our protocol from the list
protocols = protocols.replace( PROTOCOL_PACKAGE + '|' , "" );
System.setProperty( strProtocolProp, protocols );
}
}
private static URLClassLoaderWrapper findUrlLoader() {
ClassLoader loader = TypeSystem.getGosuClassLoader().getActualLoader();
if (loader instanceof URLClassLoader) {
return new SunURLClassLoaderWrapper((URLClassLoader) loader);
}
else {
Class<?> ijUrlClassLoaderClass = findSuperClass(loader.getClass(), IJUrlClassLoaderWrapper.CLASS_NAME);
if (ijUrlClassLoaderClass != null) {
return new IJUrlClassLoaderWrapper(loader, ijUrlClassLoaderClass);
}
}
throw new IllegalStateException("class loader not identified as a URL-based loader: " + loader.getClass().getName());
}
private static Class<?> findSuperClass(Class<?> loaderClass, String possibleSuperClassName) {
if (loaderClass == null) {
return null;
}
if (loaderClass.getName().equals(possibleSuperClassName)) {
return loaderClass;
}
return findSuperClass(loaderClass.getSuperclass(), possibleSuperClassName);
}
public synchronized static boolean init() {
if( addOurProtocolPackage() ) {
if( Gosu.bootstrapGosuWhenInitiatedViaClassfile() ) {
// Assuming we are in runtime, we push the root module in the case where the process was started with java.exe and not gosu.cmd
// In other words a Gosu class can be loaded directly from classfile in a bare bones Java program where only the Gosu runtime is
// on the classpath and no module was pushed prior to loading.
TypeSystem.pushModule( TypeSystem.getGlobalModule() );
}
}
addGosuClassProtocolToClasspath();
return true;
}
public synchronized static void cleanup() {
removeOurProtocolPackage();
// XXX: We can't remove URL from classloader easily.
//removeGosuClassProtocolToClasspath();
removeOurProtocolHandler();
}
private static abstract class URLClassLoaderWrapper {
final ClassLoader _loader;
final Class _classLoaderClass;
URLClassLoaderWrapper(ClassLoader loader, Class classLoaderClass) {
_loader = loader;
_classLoaderClass = classLoaderClass;
}
abstract void addURL(URL url);
abstract List<URL> getURLs();
Object invokeMethod(String methodName, Class<?>[] paramTypes, Object[] params) {
try {
Method method = _classLoaderClass.getDeclaredMethod( methodName, paramTypes );
method.setAccessible(true);
return method.invoke(_loader, params);
} catch (NoSuchMethodException e) {
throw new RuntimeException( e );
} catch (IllegalAccessException e) {
throw new RuntimeException( e );
} catch (InvocationTargetException e) {
throw new RuntimeException( e );
}
}
}
private static class SunURLClassLoaderWrapper extends URLClassLoaderWrapper {
SunURLClassLoaderWrapper(URLClassLoader loader) {
super(loader, URLClassLoader.class);
}
@Override
void addURL(URL url) {
invokeMethod("addURL", new Class[] { URL.class }, new Object[] { url });
}
@Override
List<URL> getURLs() {
return Arrays.asList(((URLClassLoader) _loader).getURLs());
}
}
private static class IJUrlClassLoaderWrapper extends URLClassLoaderWrapper {
static final String CLASS_NAME = "com.intellij.util.lang.UrlClassLoader";
IJUrlClassLoaderWrapper(ClassLoader loader, Class<?> classLoaderClass) {
super(loader, classLoaderClass);
}
@Override
void addURL(URL url) {
invokeMethod("addURL", new Class<?>[] { URL.class }, new Object[] { url });
}
@Override
List<URL> getURLs() {
//noinspection unchecked
return (List<URL>) invokeMethod("getUrls", new Class<?>[0], new Object[0]);
}
}
}