/*
* Copyright 2013 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.ICompilableTypeInternal;
import gw.internal.gosu.parser.IGosuClassInternal;
import gw.internal.gosu.parser.IGosuProgramInternal;
import gw.internal.gosu.parser.ModuleClassLoader;
import gw.internal.gosu.parser.NewIntrospector;
import gw.internal.gosu.parser.TypeLord;
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.GosuClassPathThing;
import gw.lang.reflect.gs.ICompilableType;
import gw.lang.reflect.gs.IGosuClassLoader;
import gw.lang.reflect.java.IJavaBackedType;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.module.TypeSystemLockHelper;
import gw.util.GosuExceptionUtil;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
public class GosuClassLoader implements IGosuClassLoader
{
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();
}
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();
}
else
{
_loader = parent;
}
}
private void init()
{
// Keep parse trees
}
public ClassLoader getLoader()
{
return _loader;
}
@Override
public Class loadClass( String strName ) throws ClassNotFoundException
{
TypeSystemLockHelper.getTypeSystemLockWithMonitor(_loader);
try
{
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();
}
return _loader.loadClass( strName );
}
finally
{
TypeSystem.unlock();
}
}
@Override
public Class<?> findClass( String strName ) throws ClassNotFoundException {
return loadClass( strName );
}
@Override
public IJavaType getFunctionClassForArity(int length)
{
return FunctionClassUtil.getFunctionClassForArity( 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 ) )
{
// 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() );
}
}
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 = getJavaName( gsClass );
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() )
{
ICompilableTypeInternal enclosingType = gsClass.getEnclosingType();
ClassLoader enclosingLoader = getClassLoader( enclosingType );
SingleServingGosuClassLoader loader = enclosingLoader instanceof SingleServingGosuClassLoader
? (SingleServingGosuClassLoader)enclosingLoader
: new SingleServingGosuClassLoader( this );
return defineClassInSingleServingLoader( gsClass, loader );
}
else
{
return defineAndMaybeVerify( gsClass );
}
}
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<IGosuClassLoadingObserver> observers = CommonServices.getEntityAccess().getGosuClassLoadingObservers();
if (observers != null) {
for (IGosuClassLoadingObserver observer : observers) {
if (observer.shouldUseSingleServingLoader(gsClass)) {
return true;
}
}
}
return false;
}
public byte[] maybeDefineInterfaceMethodsClass( ICompilableType gsClass )
{
return maybeDefineInterfaceMethodsClass( gsClass, false );
}
public byte[] maybeDefineInterfaceMethodsClass( ICompilableType gsClass, boolean bSaveBytes )
{
if( gsClass.isInterface() && gsClass instanceof IGosuClassInternal )
{
return TransformingCompiler.compileInterfaceMethodsClass( (IGosuClassInternal)gsClass, shouldDebugClass( gsClass ) );
}
return null;
}
public String getInterfaceMethodsClassName( ICompilableType gsClass )
{
return getJavaName( gsClass ) + "." + IGosuClassInternal.ANNOTATION_METHODS_FOR_INTERFACE_INNER_CLASS;
}
boolean shouldDebugClass( ICompilableType gsClass )
{
return BytecodeOptions.shouldDebug( gsClass.getName() );
}
private Class defineAndMaybeVerify( ICompilableTypeInternal gsClass )
{
try
{
GosuClassPathThing.init();
String strJavaClass = getJavaName( gsClass );
Class cls = _loader.loadClass( strJavaClass );
if( BytecodeOptions.aggressivelyVerify() )
{
NewIntrospector.getDeclaredMethods( cls );
cls.getDeclaredMethods(); //force verification
}
return cls;
}
catch( ClassFormatError ve )
{
if( BytecodeOptions.aggressivelyVerify() )
{
compileClass( gsClass, true ); // print the bytecode
}
throw ve;
}
catch( 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 String getJavaName( ICompilableType type )
{
if( type != null )
{
type = TypeLord.getPureGenericType( type );
IType outerType = type.getEnclosingType();
if( outerType != null )
{
return getJavaName( outerType ) + "$" + type.getRelativeName();
}
return type.getName();
}
return null;
}
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
{
Method defineClass = ClassLoader.class.getDeclaredMethod( "defineClass", String.class, byte[].class, int.class, int.class );
defineClass.setAccessible( true );
return (Class)defineClass.invoke( _loader, name, bytes, 0, bytes.length );
}
catch( Exception e )
{
throw GosuExceptionUtil.forceThrow( e );
}
finally
{
TypeSystem.unlock();
}
}
public static String getJavaName( IType type )
{
if( type != null )
{
type = TypeLord.getPureGenericType( type );
IType outerType = type.getEnclosingType();
if( outerType != null )
{
return getJavaName( outerType ) + "$" + type.getRelativeName();
}
return type.getName();
}
return null;
}
}