org.eclipse.sisu.wire.GlueLoader Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2008-present Stuart McCulloch
* 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
*
* Contributors:
* Stuart McCulloch - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.wire;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.ConcurrentMap;
import javax.inject.Provider;
import org.eclipse.sisu.inject.Weak;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
/**
* Weak cache of {@link ClassLoader}s that can generate proxy classes on-demand.
*/
final class GlueLoader
extends ClassLoader
{
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
private static final Object SYSTEM_LOADER_LOCK = new Object();
private static final String PROVIDER_NAME = Provider.class.getName();
private static final String GLUE_SUFFIX = "$__sisu__$";
private static final String DYNAMIC = "dyn";
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private static final ConcurrentMap cachedGlue = Weak.concurrentValues();
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
GlueLoader()
{
// use system loader as parent
}
GlueLoader( final ClassLoader parent )
{
super( parent );
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* Generates a new dynamic proxy instance for the given facade type and provider.
*
* @param type The facade type
* @param provider The provider
* @return Generated proxy instance
*/
@SuppressWarnings( "unchecked" )
public static T dynamicGlue( final TypeLiteral type, final Provider provider )
{
try
{
return (T) dynamicGlue( type.getRawType() ).getConstructor( Provider.class ).newInstance( provider );
}
catch ( final Exception e )
{
final Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
throw new ProvisionException( "Error proxying: " + type, cause );
}
catch ( final LinkageError e )
{
throw new ProvisionException( "Error proxying: " + type, e );
}
}
// ----------------------------------------------------------------------
// Class-loading methods
// ----------------------------------------------------------------------
@Override
@SuppressWarnings( "sync-override" )
protected Class> loadClass( final String name, final boolean resolve )
throws ClassNotFoundException
{
// ensure our proxies have access to the following non-JDK types
if ( PROVIDER_NAME.equals( name ) )
{
return Provider.class;
}
return super.loadClass( name, resolve );
}
@Override
protected Class> findClass( final String name )
throws ClassNotFoundException
{
if ( name.endsWith( GLUE_SUFFIX + DYNAMIC ) )
{
final Class> facade = loadClass( unwrap( name ) );
final byte[] code = DynamicGlue.generateProxyClass( name.replace( '.', '/' ), facade );
return defineClass( name, code, 0, code.length );
}
throw new ClassNotFoundException( name );
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
/**
* Loads the dynamic proxy class for the given facade class.
*/
private static Class> dynamicGlue( final Class> facade )
throws ClassNotFoundException
{
return glue( facade.getClassLoader() ).loadClass( wrap( facade.getName(), DYNAMIC ) );
}
/**
* Wraps the given class name with the appropriate proxy decoration.
*/
private static String wrap( final String name, final String kind )
{
final StringBuilder buf = new StringBuilder();
if ( name.startsWith( "java." ) || name.startsWith( "java/" ) )
{
buf.append( '$' ); // proxy java.* types by changing the package space
}
return buf.append( name ).append( GLUE_SUFFIX ).append( kind ).toString();
}
/**
* Unwraps the proxy decoration from around the given class name.
*/
private static String unwrap( final String name )
{
final int head = '$' == name.charAt( 0 ) ? 1 : 0;
final int tail = name.lastIndexOf( GLUE_SUFFIX );
return tail > 0 ? name.substring( head, tail ) : name;
}
/**
* Returns the {@link GlueLoader} associated with the given {@link ClassLoader}.
*/
@SuppressWarnings( "boxing" )
private static GlueLoader glue( final ClassLoader parent )
{
int id = System.identityHashCode( parent );
GlueLoader result = cachedGlue.get( id );
if ( null == result || result.getParent() != parent )
{
synchronized ( null != parent ? parent : SYSTEM_LOADER_LOCK )
{
final GlueLoader glue = createGlue( parent );
do
{
result = cachedGlue.putIfAbsent( id++, glue );
if ( null == result )
{
return glue;
}
}
while ( result.getParent() != parent );
}
}
return result;
}
/**
* Returns new {@link GlueLoader} that delegates to the given {@link ClassLoader}.
*/
private static GlueLoader createGlue( final ClassLoader parent )
{
return AccessController.doPrivileged( new PrivilegedAction()
{
public GlueLoader run()
{
return null != parent ? new GlueLoader( parent ) : new GlueLoader();
}
} );
}
}