All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
gw.internal.gosu.compiler.GosuClassLoader Maven / Gradle / Ivy
/*
* Copyright 2014 Guidewire Software, Inc.
*/
package gw.internal.gosu.compiler;
import gw.config.CommonServices;
import gw.internal.gosu.ir.TransformingCompiler;
import gw.internal.gosu.ir.transform.AbstractElementTransformer;
import gw.internal.gosu.parser.ExecutionEnvironment;
import gw.internal.gosu.parser.ICompilableTypeInternal;
import gw.internal.gosu.parser.IGosuClassInternal;
import gw.internal.gosu.parser.IGosuProgramInternal;
import gw.internal.gosu.parser.JavaMethodCache;
import gw.internal.gosu.parser.ModuleClassLoader;
import gw.internal.gosu.parser.TypeLord;
import gw.lang.parser.TypeSystemAwareCache;
import gw.lang.reflect.IGosuClassLoadingObserver;
import gw.lang.reflect.IHasJavaClass;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.BytecodeOptions;
import gw.lang.reflect.gs.ICompilableType;
import gw.lang.reflect.gs.IGosuClassLoader;
import gw.lang.reflect.gs.IGosuProgram;
import gw.lang.reflect.java.IJavaBackedType;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.java.JavaTypes;
import gw.lang.reflect.module.TypeSystemLockHelper;
import gw.util.GosuExceptionUtil;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import manifold.internal.runtime.Bootstrap;
import manifold.internal.runtime.UrlClassLoaderWrapper;
import manifold.util.ReflectUtil;
public class GosuClassLoader implements IGosuClassLoader
{
private DiscreteLoaderCache _discreteLoaders = new DiscreteLoaderCache();
private ClassLoader _loader;
//## For tests only
public static GosuClassLoader instance()
{
return (GosuClassLoader)TypeSystem.getGosuClassLoader();
}
@Override
public void dumpAllClasses()
{
// We also need to clear out anything added to some static map of random stuff due
// to the compilation of the now-orphaned classes. Side-note: this doesn't need to
// be an instance method, since it's calling static helpers, but it's an instance
// method at the moment so it can appear on IGosuClassLoader and make it through
// the wall and be usable from gosu-core-api
AbstractElementTransformer.clearCustomRuntimes();
}
@Override
public byte[] getBytes( ICompilableType gsClass )
{
try
{
return compileClass( gsClass, false );
}
catch( Exception pre )
{
throw GosuExceptionUtil.forceThrow( new IOException( pre ) );
}
}
public GosuClassLoader( ClassLoader parent )
{
assignParent( parent );
init();
}
// Note this really assigns the actual class loader associated with Gosu (now that Gosu loads its classes in the java App loader)
public void assignParent( ClassLoader parent )
{
if( parent instanceof ModuleClassLoader && ((ModuleClassLoader)parent).isDeferToParent() )
{
// For a given module, the loader that loads Gosu classes *must* also be the loader that loads Java classes.
// In the case where we are using the single default module (the normal mode for Gosu's runtime) the module
// class loader does nothing, it doesn't have a classpath, so we don't want to add our gosuclass protocol to
// its classpath, instead we want to add it to its parent, which has the classpath for the everything, including
// the Java classfiled (and potential Gosu classfiles) in the module.
// The driving force behind doing this is that, if gosu classes are persisted to disk, we want Java to load
// them as a normal class, but we also need for the loader to be able to resolve Gosu classes that may not
// have (or could not have) been written to disk. For instance, fragments and dynamic programs are compiled
// at runtime and therefore can't be precompiled -- they are compiled on demand so the gosuclass protocol,
// being in the classpath of the app class loader, resolves the name and compile the class and produce the
// resource/stream associated with the compiled bytes.
_loader = parent.getParent();
while( _loader instanceof URLClassLoader )
{
if( ((URLClassLoader) _loader).getURLs().length != 0 )
{
break;
}
if( !UrlClassLoaderWrapper.canWrap( _loader.getParent() ) )
{
break;
}
_loader = _loader.getParent();
}
}
else
{
_loader = parent;
}
}
private void init()
{
// Keep parse trees
}
public ClassLoader getLoader()
{
return _loader;
}
@Override
public Class loadClass( String strName ) throws ClassNotFoundException
{
String strGsName = strName.replace( '$', '.' );
//## hack:
if (strGsName.startsWith("com.guidewire.commons.metadata.proxy._generated.iface.")) {
strGsName = "entity." + strGsName.substring(strName.lastIndexOf('.') + 1);
}
IType type = TypeSystem.getByFullNameIfValid( strGsName );
if( type instanceof IGosuClassInternal )
{
return ((IGosuClassInternal)type).getBackingClass();
}
else if( type instanceof IJavaBackedType )
{
return ((IJavaBackedType)type).getBackingClass();
}
TypeSystemLockHelper.getTypeSystemLockWithMonitor(_loader);
try
{
return _loader.loadClass( strName );
}
finally
{
TypeSystem.unlock();
}
}
@Override
public Class> findClass( String strName ) throws ClassNotFoundException {
return loadClass( strName );
}
@Override
public IJavaType getFunctionClassForArity( boolean hasReturn, int length )
{
return FunctionClassUtil.getFunctionClassForArity( hasReturn, length );
}
public Class defineClass( ICompilableTypeInternal gsClass, boolean useSingleServingLoader ) throws ClassNotFoundException
{
try
{
if( gsClass instanceof IGosuClassInternal && ((IGosuClassInternal)gsClass).hasBackingClass() )
{
return ((IGosuClassInternal)gsClass).getBackingClass();
}
// there is no point in defining eval classes in a single serving class loader (it wastes memory)
if( useSingleServingLoader || TypeLord.isEvalProgram( gsClass ) || isThrowawayProgram( gsClass ) || isEnclosingTypeInSingleServingLoader( gsClass ) || hasDiscreteNamespace( gsClass.getNamespace() ) )
{
// These classes are "fire and forget"; they need to be disposable after they run,
// so we load them in a separate class loader so we can unload them -- it's the only
// way to unload a class in java.
return defineClassInLoader( gsClass, true );
}
return findOrDefineClass( gsClass );
}
catch( Exception e )
{
throw GosuExceptionUtil.forceThrow( e, gsClass.getName() );
}
}
boolean hasDiscreteNamespace( String namespace )
{
return _discreteLoaders.getLoader( namespace ) != null;
}
DiscreteClassLoader getDiscreteNamespaceLoader( String namespace )
{
return _discreteLoaders.getLoader( namespace );
}
@SuppressWarnings("UnusedDeclaration")
public boolean isLoaderUnloaded( String namespace )
{
return _discreteLoaders.isLoaderUnloaded( namespace );
}
private boolean isEnclosingTypeInSingleServingLoader( ICompilableTypeInternal gsClass )
{
ICompilableTypeInternal enclosingType = gsClass.getEnclosingType();
ClassLoader enclosingLoader = getClassLoader( enclosingType );
return enclosingLoader instanceof SingleServingGosuClassLoader;
}
private Class findOrDefineClass( ICompilableTypeInternal gsClass ) throws ClassNotFoundException
{
String strName = gsClass.getJavaName();
Class cls = null;
try
{
cls = _loader.loadClass( strName );
if( cls.getClassLoader() instanceof SingleServingGosuClassLoader )
{
if( ((SingleServingGosuClassLoader)cls.getClassLoader()).isDisposed() )
{
cls = null;
}
}
}
catch( ClassNotFoundException cnfe )
{
TypeSystem.lock();
try
{
cls = defineClassInLoader( gsClass, false );
}
finally
{
TypeSystem.unlock();
}
}
return cls;
}
private Class defineClassInLoader( ICompilableTypeInternal gsClass, boolean forceSingleServingLoader )
{
if( forceSingleServingLoader || shouldUseSingleServingLoader( gsClass ) || BytecodeOptions.isSingleServingLoader() )
{
SingleServingGosuClassLoader loader = getOrCreateSingleServingLoader( gsClass );
return defineClassInSingleServingLoader( gsClass, loader );
}
else
{
return defineAndMaybeVerify( gsClass );
}
}
private SingleServingGosuClassLoader getOrCreateSingleServingLoader( ICompilableTypeInternal gsClass )
{
ICompilableTypeInternal enclosingType = gsClass.getEnclosingType();
ClassLoader enclosingLoader = isOldStyleGosuAnnotationExpression(gsClass) ? null : getClassLoader( enclosingType );
if( enclosingLoader instanceof SingleServingGosuClassLoader && !(enclosingLoader instanceof DiscreteClassLoader) )
{
return (SingleServingGosuClassLoader)enclosingLoader;
}
DiscreteClassLoader namespaceLoader = getDiscreteNamespaceLoader( gsClass.getNamespace() );
return namespaceLoader == null ? new SingleServingGosuClassLoader( this ) : namespaceLoader;
}
private boolean isOldStyleGosuAnnotationExpression(ICompilableTypeInternal gsClass)
{
if( !(gsClass instanceof IGosuProgram) )
{
return false;
}
final IType expectedReturnType = ((IGosuProgram) gsClass).getExpectedReturnType();
return expectedReturnType != null && JavaTypes.IANNOTATION().isAssignableFrom( expectedReturnType );
}
private ClassLoader getClassLoader( ICompilableTypeInternal enclosingType ) {
if( enclosingType == null ) {
return null;
}
Class cached = SingleServingGosuClassLoader.getCached( enclosingType );
if( cached != null ) {
return cached.getClassLoader();
}
return enclosingType instanceof IJavaBackedType
? ((IJavaBackedType)enclosingType).getBackingClass().getClassLoader()
: enclosingType instanceof IHasJavaClass
? ((IHasJavaClass)enclosingType).getBackingClass().getClassLoader()
: null;
}
private Class> defineClassInSingleServingLoader( ICompilableTypeInternal gsClass, SingleServingGosuClassLoader loader ) {
Class> result = loader._defineClass( gsClass );
// Define all inner classes and blocks, too. Otherwise, they eventually could be loaded through URL handler.
for (int i = 0; i < gsClass.getBlockCount(); i++) {
defineClassInSingleServingLoader( (ICompilableTypeInternal)gsClass.getBlock(i), loader );
}
if( gsClass.getInnerClasses() != null ) {
for( IType inner: gsClass.getInnerClasses() ) {
try {
defineClassInSingleServingLoader( (ICompilableTypeInternal)inner, loader );
}
catch( LinkageError le ) {
// ignore case when we've already loaded the class
}
}
}
return result;
}
private boolean shouldUseSingleServingLoader(ICompilableTypeInternal gsClass) {
List observers = CommonServices.getEntityAccess().getGosuClassLoadingObservers();
if (observers != null) {
for (IGosuClassLoadingObserver observer : observers) {
if (observer.shouldUseSingleServingLoader(gsClass)) {
return true;
}
}
}
return hasDiscreteNamespace( gsClass.getNamespace() );
}
boolean shouldDebugClass( ICompilableType gsClass )
{
return BytecodeOptions.shouldDebug( gsClass.getName() );
}
private Class defineAndMaybeVerify( ICompilableTypeInternal gsClass )
{
try
{
Bootstrap.init();
String strJavaClass = gsClass.getJavaName();
Class cls = _loader.loadClass( strJavaClass );
if( BytecodeOptions.aggressivelyVerify() )
{
JavaMethodCache.getDeclaredMethods( cls );
cls.getDeclaredMethods(); //force verification
}
return cls;
}
catch( ClassFormatError | VerifyError ve )
{
if( BytecodeOptions.aggressivelyVerify() )
{
compileClass( gsClass, true ); // print the bytecode
}
throw ve;
}
catch( ClassNotFoundException e )
{
throw new RuntimeException( e );
}
}
private static byte[] compileClass( ICompilableType type, boolean debug )
{
return TransformingCompiler.compileClass( type, debug );
}
private boolean isThrowawayProgram( ICompilableType gsClass ) {
return gsClass instanceof IGosuProgramInternal && ((IGosuProgramInternal) gsClass).isThrowaway();
}
@Override
public ClassLoader getActualLoader() {
return getLoader();
}
@Override
public Class defineClass( String name, byte[] bytes )
{
TypeSystem.lock();
try
{
return (Class)ReflectUtil.method( ClassLoader.class, "defineClass", String.class, byte[].class, int.class, int.class )
.invoke( _loader, name, bytes, 0, bytes.length );
}
catch( Exception e )
{
throw GosuExceptionUtil.forceThrow( e );
}
finally
{
TypeSystem.unlock();
}
}
public boolean waitForLoaderToUnload( String packageName, long millisToWait )
{
long lStart = System.currentTimeMillis();
while( !_discreteLoaders.isLoaderUnloaded( packageName ) )
{
if( System.currentTimeMillis() - lStart > millisToWait )
{
return false;
}
System.gc();
try
{
Thread.sleep( 100 );
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
}
return true;
}
public void evictLoader( String packageName )
{
_discreteLoaders.evictLoader( packageName );
}
private class DiscreteLoaderCache
{
// Wrap a delegate to hide the TypeSystemAwareCache#get() method
private DelegateCache _delegate = new DelegateCache();
public DiscreteClassLoader getLoader( String key )
{
if( getDiscretePackages().isEmpty() )
{
return null;
}
return _delegate.getLoader( key );
}
public boolean isLoaderUnloaded( String key )
{
if( getDiscretePackages().isEmpty() )
{
return true;
}
return _delegate.get( key ).get() == null;
}
public void evictLoader( String key )
{
_delegate.evict( key );
}
private List getDiscretePackages()
{
String[] discretePackages = ExecutionEnvironment.instance().getDiscretePackages();
return discretePackages == null ? Collections.emptyList() : Arrays.asList( discretePackages );
}
private class DelegateCache extends TypeSystemAwareCache>
{
public DelegateCache()
{
//noinspection Convert2Lambda
super( "Discrete Loaders", 100,
new MissHandler>()
{
@Override
public WeakReference load( String key )
{
Optional match = getDiscretePackages().stream().filter( key::startsWith ).findFirst();
if( !match.isPresent() )
{
return new WeakReference<>( DiscreteClassLoader.NULL_SENTINAL );
}
String unloadableNamespace = match.get();
if( key.equals( unloadableNamespace ) )
{
return new WeakReference<>( null );
}
return _delegate.get( unloadableNamespace );
}
} );
}
private DiscreteClassLoader getLoader( String key )
{
WeakReference ref = super.get( key );
if( ref.get() != DiscreteClassLoader.NULL_SENTINAL )
{
// Must maintain a ref to the loader, otherwise it can get collected in between assigning it to the weak ref and returning from this method, in theory anyway
//noinspection MismatchedReadAndWriteOfArray
DiscreteClassLoader[] loader = new DiscreteClassLoader[1];
if( ref.get() == null )
{
String unloadableNamespace = getDiscretePackages().stream()
.filter( ( t ) -> {
if( key.equals( t ) )
{
return true;
}
if( !t.endsWith( "." ) )
{
t = t + '.';
}
return key.startsWith( t );
} ).findFirst().get();
put( unloadableNamespace, ref = new WeakReference<>( loader[0] = new DiscreteClassLoader( unloadableNamespace, GosuClassLoader.this ) ) );
}
return ref.get();
}
return null;
}
}
}
}